💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## JMH是什么 JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。该工具是由 Oracle 内部实现 JIT 的大牛们编写的,他们应该比任何人都了解 JIT 以及 JVM 对于基准测试的影响。 ## 应用场景 1. 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性 2. 对比接口不同实现在给定条件下的吞吐量 3. 查看多少百分比的请求在多长时间内完成 ## 如何使用 1. 引入依赖,jdk9自带,低于9的版本需要自行引入 ~~~ <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.23</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.23</version> </dependency> ~~~ 2. 对比使用`+`号连接字符串和`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(); } } ~~~ 3. 运行后得出如下输出,且会保持结果数据到`result.json`中 过程信息如下:(其中一段) ``` # JMH version: 1.23 # VM version: JDK 1.8.0_221, Java HotSpot(TM) 64-Bit Server VM, 25.221-b11 # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/bin/java # VM options: -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=59182:/Applications/IntelliJ IDEA CE.app/Contents/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: org.mango.demo.mt.jmh.StringConnectTest.testStringBuilderAdd # Parameters: (length = 100) # Run progress: 83.33% complete, ETA 00:00:29 # Fork: 1 of 1 # Warmup Iteration 1: 2834.364 ±(99.9%) 1230.495 ns/op # Warmup Iteration 2: 2350.791 ±(99.9%) 397.253 ns/op # Warmup Iteration 3: 2150.000 ±(99.9%) 642.696 ns/op Iteration 1: 2174.173 ±(99.9%) 343.439 ns/op Iteration 2: 2256.638 ±(99.9%) 85.831 ns/op Iteration 3: 4466.223 ±(99.9%) 161.050 ns/op Iteration 4: 2378.494 ±(99.9%) 107.129 ns/op Iteration 5: 2259.465 ±(99.9%) 398.071 ns/op Result "org.mango.demo.mt.jmh.StringConnectTest.testStringBuilderAdd": 2706.999 ±(99.9%) 3797.231 ns/op [Average] (min, avg, max) = (2174.173, 2706.999, 4466.223), stdev = 986.129 CI (99.9%): [≈ 0, 6504.229] (assumes normal distribution) # Run complete. Total time: 00:02:55 ``` 结果信息输出如下: ``` REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. Benchmark (length) Mode Cnt Score Error Units StringConnectTest.testStringAdd 10 avgt 5 466.614 ± 578.636 ns/op StringConnectTest.testStringAdd 50 avgt 5 3100.612 ± 246.366 ns/op StringConnectTest.testStringAdd 100 avgt 5 13679.748 ± 10239.498 ns/op StringConnectTest.testStringBuilderAdd 10 avgt 5 242.057 ± 240.442 ns/op StringConnectTest.testStringBuilderAdd 50 avgt 5 1376.971 ± 1211.945 ns/op StringConnectTest.testStringBuilderAdd 100 avgt 5 2706.999 ± 3797.231 ns/op Benchmark result is saved to result.json ``` ## 结果可视化 http://deepoove.com/jmh-visual-chart/: ![](https://img.kancloud.cn/85/fb/85fb329ba2c73643bdb0916a50a55a69_1536x976.png) https://jmh.morethan.io/: ![](https://img.kancloud.cn/01/c8/01c839f55105d0a4e13f6a68eb487da0_2722x800.png) ## jar包方式部署运行 小型的测试,直接本地运行Main方法测试即可;但是对于一些特定环境或者大型测试,需要放到服务器上跑才行,所以需要jar包方式运行。 使用Maven打包插件得到jar包 ~~~ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>jmh-demo</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.openjdk.jmh.Main</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> ~~~ 执行如下命令,运行测试: ``` java -jar jmh-demo.jar StringConnectTest ``` ## JMH基础 为了能够更好地使用 JMH 的各项功能,下面对 JMH 的基本概念进行讲解: ### @BenchmarkMode ![](https://img.kancloud.cn/40/d7/40d75864e0d4296bdc4968f78537d388_1328x440.png) ### @State ![](https://img.kancloud.cn/b8/84/b8843b30bdb63b0ad85e30dc9dd3abbf_1316x350.png) ### @OutputTimeUnit 为统计结果的时间单位,可用于类或者方法注解 ### @Warmup ![](https://img.kancloud.cn/52/f3/52f3443b1ba00c56ef90cd5128a114e1_1336x540.png) ### @Measurement 实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和`@Warmup`相同。 ### @Threads 每个进程中的测试线程,可用于类或者方法上。 ### @Fork 进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。 ### @Param 指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义 @State 注解。 ## JMH插件 idea插件:https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness ## JMH陷阱 ![](https://img.kancloud.cn/44/65/44653aa6ee3de844314ca024f9d6391d_1352x1116.png) ## 参考 > 为什么要用JMH?何时应该用? - 武培轩的回答 - 知乎 https://www.zhihu.com/question/276455629/answer/1259967560 > 葛一鸣 * 《Java高并发程序设计》