在软件开发中,性能优化是一个永恒的话题。为了确保代码在生产环境中运行得尽可能快,开发者需要一种准确的方法来度量和比较不同代码片段的性能。Java Microbenchmark Harness(JMH)是一个专门为Java和其他基于JVM的语言设计的工具,它允许开发者以高精度执行微基准测试。
JMH是一个用于编写可靠Java微基准测试的工具。它可以帮助开发者量化代码片段的执行时间,这对于理解代码性能至关重要。通过JMH,开发者可以比较不同算法或代码实现的性能,从而做出基于数据的优化决策。
JMH的设计考虑了基准测试中的各种陷阱,如JVM的热点优化、死码消除和垃圾收集暂停。它提供了一组注解和工具类,使得编写、配置和运行基准测试变得简单而直观。
三、使用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
Scope
参数,用于指定状态实例的生命周期和共享范围。Scope.Thread
:每个测试线程分配一个状态实例。Scope.Benchmark
:所有测试线程共享一个状态实例。Scope.Group
:每个线程组共享一个状态实例。java.util.concurrent.TimeUnit
中的标准时间单位。Mode
枚举值包括:Throughput
(吞吐量),AverageTime
(平均时间),SampleTime
(随机采样时间),SingleShotTime
(单次执行时间),All
(所有模式)。Scope.Group
时)。比较两种字符串拼接方法的性能:使用+操作符和使用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
类来执行基准测试。
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
类来执行基准测试。执行结果将包括每个数组大小和每种排序方法的平均执行时间。
JMH是一个强大而灵活的工具,用于在Java和其他基于JVM的语言中进行微基准测试。通过掌握JMH的核心特性和最佳实践,开发者可以准确地度量和比较代码的性能,从而做出明智的优化决策。在性能关键的场景中,使用JMH进行基准测试是确保代码高效运行的关键步骤。