Java 性能调优必备利器——JMH
点击关注公众号,Java干货及时送达
JMH 简介
想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性 对比接口不同实现在给定条件下的吞吐量 查看多少百分比的请求在多长时间内完成
加入依赖
1.23
):
org.openjdk.jmh
jmh-core
1.23
org.openjdk.jmh
jmh-generator-annprocess
1.23
编写基准测试
+
和 StringBuilder.append()
两种字符串拼接哪个耗时更短,具体代码如下所示:@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectTest {
@Param(value = {"10", "50", "100"})
private int length;
@Benchmark
public void testStringAdd(Blackhole blackhole) {
String a = "";
for (int i = 0; i < length; i++) {
a += i;
}
blackhole.consume(a);
}
@Benchmark
public void testStringBuilderAdd(Blackhole blackhole) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(i);
}
blackhole.consume(sb.toString());
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(StringConnectTest.class.getSimpleName())
.result("result.json")
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
}
@Benchmark
注解标识,这些注解的具体含义将在下面介绍。最新面试题整理好了,点击Java面试库小程序在线刷题。http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
执行基准测试
# JMH version: 1.23
# VM version: JDK 1.8.0_201, Java HotSpot(TM) 64-Bit Server VM, 25.201-b09
# VM invoker: D:\Software\Java\jdk1.8.0_201\jre\bin\java.exe
# VM options: -javaagent:D:\Software\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=61018:D:\Software\JetBrains\IntelliJ IDEA 2019.1.3\bin -Dfile.encoding=UTF-8
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 4 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.wupx.jmh.StringConnectTest.testStringBuilderAdd
# Parameters: (length = 100)
# Warmup Iteration 1: 1083.569 ±(99.9%) 393.884 ns/op
# Warmup Iteration 2: 864.685 ±(99.9%) 174.120 ns/op
# Warmup Iteration 3: 798.310 ±(99.9%) 121.161 ns/op
Iteration 1: 810.667 ±(99.9%) 51.505 ns/op
Iteration 2: 807.861 ±(99.9%) 13.163 ns/op
Iteration 3: 851.421 ±(99.9%) 33.564 ns/op
Iteration 4: 805.675 ±(99.9%) 33.038 ns/op
Iteration 5: 821.020 ±(99.9%) 66.943 ns/op
Result "com.wupx.jmh.StringConnectTest.testStringBuilderAdd":
819.329 ±(99.9%) 72.698 ns/op [Average]
(min, avg, max) = (805.675, 819.329, 851.421), stdev = 18.879
CI (99.9%): [746.631, 892.027] (assumes normal distribution)
Benchmark (length) Mode Cnt Score Error Units
StringConnectTest.testStringBuilderAdd 100 avgt 5 819.329 ± 72.698 ns/op
testStringBuilderAdd
方法的平均执行花费时间为 819.329 ns
,误差为 72.698 ns
。Benchmark (length) Mode Cnt Score Error Units
StringConnectTest.testStringAdd 10 avgt 5 161.496 ± 17.097 ns/op
StringConnectTest.testStringAdd 50 avgt 5 1854.657 ± 227.902 ns/op
StringConnectTest.testStringAdd 100 avgt 5 6490.062 ± 327.626 ns/op
StringConnectTest.testStringBuilderAdd 10 avgt 5 68.769 ± 4.460 ns/op
StringConnectTest.testStringBuilderAdd 50 avgt 5 413.021 ± 30.950 ns/op
StringConnectTest.testStringBuilderAdd 100 avgt 5 819.329 ± 72.698 ns/op
StringBuilder.append()
的性能就更好。生成 jar 包执行
org.apache.maven.plugins
maven-shade-plugin
2.4.1
package
shade
jmh-demo
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
org.openjdk.jmh.Main
mvn clean install
java -jar target/jmh-demo.jar StringConnectTest
JMH 基础
@BenchmarkMode
@BenchmarkMode({Mode.SampleTime, Mode.AverageTime})
,还可以设置为 Mode.All
,即全部执行一遍。Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
SampleTime:随机取样,最后输出取样结果的分布 SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能 All:上面的所有模式都执行一次
@State
Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能 Scope.Group:同一个线程在同一个 group 里共享实例 Scope.Thread:默认的 State,每个测试线程分配一个实例
@OutputTimeUnit
@Warmup
iterations:预热的次数 time:每次预热的时间 timeUnit:时间的单位,默认秒 batchSize:批处理大小,每次操作调用几次方法
为什么需要预热? 因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。
@Measurement
@Warmup
相同。@Threads
@Fork
@Param
JMH 陷阱
@Benchmark
public void testStringAdd(Blackhole blackhole) {
String a = "";
for (int i = 0; i < length; i++) {
a += i;
}
}
a
从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。https://github.com/lexburner/JMH-samples
了解全部的陷阱。JMH 插件
File->Settings...->Plugins
,然后搜索 jmh,选择安装 JMH plugin:自动生成带有 @Benchmark
的方法像 JUnit 一样,运行单独的 Benchmark 方法 运行类中所有的 Benchmark 方法
Generate...
,选择操作 Generate JMH benchmark
就可以生成一个带有 @Benchmark
的方法。@Benchmark
注解的方法都会被执行。JMH 可视化
JMH Visual Chart
:http://deepoove.com/jmh-visual-chart/JMH Visualizer
:https://jmh.morethan.io/
总结
@Benchmark
注解标识,就可以让 JMH 的注解处理器自动生成真正的性能测试代码,以及相应的性能测试配置文件。关注Java技术栈看更多干货
评论