在软件开发中,性能优化是一个永恒的话题。为了确保代码在生产环境中运行得尽可能快,开发者需要一种准确的方法来度量和比较不同代码片段的性能。Java Microbenchmark Harness(JMH)是一个专门为Java和其他基于JVM的语言设计的工具,它允许开发者以高精度执行微基准测试。
1.JMH简介
JMH是一个用于编写可靠Java微基准测试的工具。它可以帮助开发者量化代码片段的执行时间,这对于理解代码性能至关重要。通过JMH,开发者可以比较不同算法或代码实现的性能,从而做出基于数据的优化决策。
JMH的设计考虑了基准测试中的各种陷阱,如JVM的热点优化、死码消除和垃圾收集暂停。它提供了一组注解和工具类,使得编写、配置和运行基准测试变得简单而直观。
2.JMH核心特性
- 注解驱动:JMH使用注解来标记基准测试方法和配置测试参数。这些注解提供了丰富的配置选项,如测试模式(吞吐量、平均时间等)、预热迭代次数、测量迭代次数等。
- 隔离测试:为了确保测试结果的可重复性,JMH会在单独的JVM进程中运行每个基准测试。这样可以避免测试之间的干扰,并确保每个测试都在相同的初始条件下运行。
- 预热和迭代:JMH允许开发者指定预热迭代次数,以使得JVM的热点优化在测量阶段之前生效。此外,通过多次迭代测试,JMH可以计算统计上显著的结果,减少偶然误差。
- 结果统计:JMH会自动收集和分析测试结果,提供有关吞吐量、平均执行时间等的详细信息。这些信息对于理解代码性能瓶颈和优化方向非常有价值。
三、使用JMH进行基准测试
使用JMH进行基准测试涉及几个步骤:添加依赖、编写基准测试类、配置测试选项和运行测试。
- 添加JMH依赖
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.33</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 编写基准测试类
创建一个Java类,并使用JMH提供的注解来标记基准测试方法。例如,使用@Benchmark
注解来标记要进行性能测量的方法,使用@BenchmarkMode
来指定测试模式(如Throughput
表示吞吐量,AverageTime
表示平均时间),以及使用@OutputTimeUnit
来指定输出结果的时间单位。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
@Benchmark
public void measure() {
// 这里放置你想要基准测试的代码
}
}
- 运行基准测试
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>true</skipTests> <!-- 禁用常规的Maven测试 -->
</configuration>
</plugin>
<plugin>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-maven-plugin</artifactId>
<version>1.33</version> <!-- 使用你需要的版本 -->
<executions>
<execution>
<id>run-benchmarks</id>
<phase>integrate-test</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
然后,你可以通过Maven命令来运行基准测试:
mvn clean integrate-test
通过JMH命令行工具运行:
mvn clean package
java -jar target/benchmarks.jar
3.JMH注解
- Benchmark:
- 这是一个方法注解,用于声明该方法是一个基准测试方法。
- 被此注解标记的方法将被JMH用于重复执行,以便进行性能测量。
- State:
- 这是一个类注解,用于声明该类是一个“状态”类。
- 状态类定义了基准测试的状态,可以包含测试所需的实例变量。
- 它有一个
Scope
参数,用于指定状态实例的生命周期和共享范围。
- Scope枚举值:
-
Scope.Thread
:每个测试线程分配一个状态实例。 -
Scope.Benchmark
:所有测试线程共享一个状态实例。 -
Scope.Group
:每个线程组共享一个状态实例。
- Setup :
- 这是一个方法注解,用于指定在基准测试方法执行之前运行的初始化方法。
- 通常用于准备测试数据或初始化状态。
- TearDown :
- 这是一个方法注解,用于指定在基准测试方法执行之后运行的清理方法。
- 通常用于释放资源或进行后处理。
- Param :
- 这是一个字段注解,用于指定基准测试的参数。
- 可以为基准测试方法提供不同的输入值,以便测试在不同条件下的性能。
- OutputTimeUnit :
- 这是一个类或方法注解,用于指定基准测试结果的时间单位。
- 它使用
java.util.concurrent.TimeUnit
中的标准时间单位。
- BenchmarkMode :
- 这是一个类或方法注解,用于指定基准测试的模式。
-
Mode
枚举值包括:Throughput
(吞吐量),AverageTime
(平均时间),SampleTime
(随机采样时间),SingleShotTime
(单次执行时间),All
(所有模式)。
- Warmup :
- 这是一个类或方法注解,用于配置预热迭代的次数。
- 预热迭代用于使JVM的热点代码优化达到稳定状态,以获得更准确的基准测试结果。
- Measurement :
- 这是一个类或方法注解,用于配置实际测量迭代的次数。
- 这些迭代将用于收集性能数据。
- Fork :
- 这是一个类注解,用于指定基准测试的进程分叉次数。
- 每个分叉将在单独的进程中运行基准测试,以减少噪声和干扰。
- Threads :
- 这是一个方法注解,用于指定执行基准测试的线程数。
- 它允许模拟多线程环境下的性能。
- Group :
- 这是一个类和方法注解,用于将多个基准测试方法组合成一个测试组。
- 测试组内的方法将按照指定的顺序执行,并且共享相同的状态实例(当使用
Scope.Group
时)。
4.简单的基准测试
比较两种字符串拼接方法的性能:使用+操作符和使用StringBuilder。
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
public class StringConcatBenchmark {
private static final String A = "Hello, ";
private static final String B = "World!";
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public String stringConcatPlus() {
return A + B;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public String stringConcatStringBuilder() {
StringBuilder sb = new StringBuilder();
sb.append(A);
sb.append(B);
return sb.toString();
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(StringConcatBenchmark.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(10)
.forks(1)
.build();
new Runner(opt).run();
}
}
这个例子中,我们定义了一个StringConcatBenchmark
类,其中包含两个基准测试方法:stringConcatPlus
和stringConcatStringBuilder
。我们使用@State(Scope.Thread)
注解来指定每个测试线程有其独立的状态实例。@BenchmarkMode(Mode.AverageTime)
和@OutputTimeUnit(TimeUnit.NANOSECONDS)
注解分别指定我们想要测量的是平均时间,并且输出结果的时间单位为纳秒。main
方法中,我们配置了基准测试的运行选项,并通过Runner
类来执行基准测试。
5.参数化基准测试
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
public class ArraySortBenchmark {
@Param({"100", "1000", "10000"})
private int arraySize;
private Integer[] array;
@Setup
public void setup() {
array = new Integer[arraySize];
Random rand = new Random();
for (int i = 0; i < arraySize; i++) {
array[i] = rand.nextInt();
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void sortArrayTimSort() {
Arrays.sort(array);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public void sortArrayJava8ParallelSort() {
Arrays.parallelSort(array);
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(ArraySortBenchmark.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.forks(1)
.build();
new Runner(opt).run();
}
}
在这个例子中,我们使用@Param
注解来定义了一个参数arraySize
,它将在基准测试中取不同的值(100、1000、10000)。@Setup
注解用于在执行基准测试之前进行一些初始化工作,在本例中是生成一个随机数组。
我们定义了两个基准测试方法:sortArrayTimSort
使用Arrays.sort
进行排序,而sortArrayJava8ParallelSort
使用Arrays.parallelSort
进行排序。我们将测量这两种方法对不同大小数组的平均排序时间。main
方法中,我们配置了基准测试的运行选项,并通过Runner
类来执行基准测试。执行结果将包括每个数组大小和每种排序方法的平均执行时间。文章来源:https://www.toymoban.com/news/detail-791998.html
6.结论
JMH是一个强大而灵活的工具,用于在Java和其他基于JVM的语言中进行微基准测试。通过掌握JMH的核心特性和最佳实践,开发者可以准确地度量和比较代码的性能,从而做出明智的优化决策。在性能关键的场景中,使用JMH进行基准测试是确保代码高效运行的关键步骤。文章来源地址https://www.toymoban.com/news/detail-791998.html
到了这里,关于Java-基准测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!