JMH 在 Java 性能基准测试中的应用与实践
Java 性能基准测试是软件开发过程中不可或缺的一部分。本文将介绍如何使用 JMH (Java Microbenchmark Harness) 工具进行精确的性能测量,并详细讲解从编写到结果分析的全过程。
1. 为什么需要性能基准测试?
在优化代码之前,我们需要明确:
- 哪些代码段是性能瓶颈?
- 新算法是否比旧算法更高效?
- 优化是否引入了新的问题?
性能基准测试可以帮助我们量化性能指标、对比不同方案、验证优化效果以及识别性能瓶颈。
2. JMH 简介
JMH 是 Oracle 提供的微基准测试工具,能够避免常见的性能测试陷阱如死代码消除和常量折叠等。JMH 提供了统计分析功能,支持多种模式和配置选项。
3. JMH 核心概念
- Benchmark: 需要测试的方法,用 `@Benchmark` 注解标记。
- State: 定义基准测试的状态,通过 `@State` 注解定义。
- Scope: 状态的作用范围,包括 `Thread`, `Benchmark`, 和 `Group`。
- Mode: 测试模式,如吞吐量 (`Throughput`) 和平均时间 (`AverageTime`)。
- Warmup: JIT 编译器预热阶段。
- Measurement: 正式测量阶段。
4. Maven 项目集成
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.36</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.36</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<finalName>benchmark</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
5. 编写 JMH 基准测试示例
import org.openjdk.jmh.annotations.*;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class StrConcatBench {
@Param({"10", "100", "1000"})
public int len;
private String strA;
private String strB;
@Setup(Level.Trial)
public void setUp() {
Random rnd = new Random();
strA = randomStr(len);
strB = randomStr(len);
}
private String randomStr(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append((char) ('a' + rnd.nextInt(26)));
}
return sb.toString();
}
@Benchmark
public String concatPlus() {
return strA + strB;
}
@Benchmark
public String concatBuilder() {
return new StringBuilder(strA).append(strB).toString();
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(StrConcatBench.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
6. 运行 JMH 基准测试
编译项目并运行:
mvn clean install java -jar target/benchmark.jar
7. 分析 JMH 结果
关注以下关键指标:
- Benchmark: 方法名称
- Mode: 测试模式
- Score: 性能得分
- Error: 误差范围
- Units: 时间单位
8. 实战案例:集合性能比较
import org.openjdk.jmh.annotations.*;
import java.util.*;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
public class ListPerfBench {
@Param({"1000", "10000", "100000"})
public int size;
private List<Integer> listA;
private List<Integer> listB;
@Setup(Level.Invocation)
public void setup() {
listA = new ArrayList<>();
listB = new LinkedList<>();
}
@Benchmark
public void arrayInsert() {
listA.add(new Random().nextInt());
}
@Benchmark
public void linkedInsert() {
listB.add(new Random().nextInt());
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(ListPerfBench.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}