一、入门

1. 使用Maven搭建基准测试项目骨架

在命令行运行

  1. mvn archetype:generate \
  2. -DinteractiveMode=false \
  3. -DarchetypeGroupId=org.openjdk.jmh \
  4. -DarchetypeArtifactId=jmh-java-benchmark-archetype \
  5. -DgroupId=cn.hdj \
  6. -DartifactId=demo-jmh \
  7. -Dversion=1.0

生成的项目
image.png
jmh 需要的依赖

  1. <dependency>
  2. <groupId>org.openjdk.jmh</groupId>
  3. <artifactId>jmh-core</artifactId>
  4. <version>1.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.openjdk.jmh</groupId>
  8. <artifactId>jmh-generator-annprocess</artifactId>
  9. <version>1.0</version>
  10. <scope>provided</scope>
  11. </dependency>

打包运行

  1. mvn clean package
  2. java -jar target/benchmarks.jar

或者直接IDE启动

  1. public static void main(String[] args) throws RunnerException {
  2. Options options = new OptionsBuilder()
  3. .include(MyBenchmark.class.getSimpleName())
  4. .build();
  5. new Runner(options).run();
  6. }

2. 测试结果说明

  1. /usr/lib/jvm/java-1.8.0-amazon-corretto/bin/java -Dvisualvm.id=1611482863790 -javaagent:/home/hdj/software/idea-IC-192.6817.14/lib/idea_rt.jar=41045:/home/hdj/software/idea-IC-192.6817.14/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/charsets.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/jfxrt.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/jce.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/jfxswt.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/jsse.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/management-agent.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/resources.jar:/usr/lib/jvm/java-1.8.0-amazon-corretto/jre/lib/rt.jar:/home/hdj/IdeaProjects/demo-jmh/target/classes:/home/hdj/.m2/repository/org/openjdk/jmh/jmh-core/1.0/jmh-core-1.0.jar:/home/hdj/.m2/repository/net/sf/jopt-simple/jopt-simple/4.6/jopt-simple-4.6.jar:/home/hdj/.m2/repository/org/apache/commons/commons-math3/3.2/commons-math3-3.2.jar cn.hdj.MyBenchmark
  2. # VM invoker: /usr/lib/jvm/java-1.8.0-amazon-corretto/jre/bin/java
  3. # VM options: -Dvisualvm.id=1611482863790 -javaagent:/home/hdj/software/idea-IC-192.6817.14/lib/idea_rt.jar=41045:/home/hdj/software/idea-IC-192.6817.14/bin -Dfile.encoding=UTF-8
  4. # Warmup: 20 iterations, 1 s each
  5. # Measurement: 20 iterations, 1 s each
  6. # Threads: 1 thread, will synchronize iterations
  7. # Benchmark mode: Throughput, ops/time
  8. # Benchmark: cn.hdj.MyBenchmark.testMethod
  9. # Run progress: 0.00% complete, ETA 00:01:20
  10. # Fork: 1 of 2
  11. # Warmup Iteration 1: 3138283965.494 ops/s
  12. # Warmup Iteration 2: 3343881235.996 ops/s
  13. # Warmup Iteration 3: 3348834653.937 ops/s
  14. # Warmup Iteration 4: 3330039593.773 ops/s
  15. # Warmup Iteration 5: 3247983530.809 ops/s
  16. # Warmup Iteration 6: 3318130296.568 ops/s
  17. # Warmup Iteration 7: 3278282514.072 ops/s
  18. # Warmup Iteration 8: 3337975530.668 ops/s
  19. # Warmup Iteration 9: 3331120417.067 ops/s
  20. # Warmup Iteration 10: 3348364691.490 ops/s
  21. # Warmup Iteration 11: 3344805534.396 ops/s
  22. # Warmup Iteration 12: 3349783444.367 ops/s
  23. # Warmup Iteration 13: 3329624986.329 ops/s
  24. # Warmup Iteration 14: 3345768149.363 ops/s
  25. # Warmup Iteration 15: 3335593072.642 ops/s
  26. # Warmup Iteration 16: 3278417352.303 ops/s
  27. # Warmup Iteration 17: 3351830850.728 ops/s
  28. # Warmup Iteration 18: 3357026177.207 ops/s
  29. # Warmup Iteration 19: 3349489692.778 ops/s
  30. # Warmup Iteration 20: 3303576642.448 ops/s
  31. Iteration 1: 3302628316.687 ops/s
  32. Iteration 2: 3355204295.771 ops/s
  33. Iteration 3: 3352822812.457 ops/s
  34. Iteration 4: 3293838208.499 ops/s
  35. Iteration 5: 3348386854.812 ops/s
  36. Iteration 6: 3356557432.133 ops/s
  37. Iteration 7: 3352918338.829 ops/s
  38. Iteration 8: 3357925024.540 ops/s
  39. Iteration 9: 3275335806.697 ops/s
  40. Iteration 10: 3346282350.779 ops/s
  41. Iteration 11: 3355910719.360 ops/s
  42. Iteration 12: 3286355804.692 ops/s
  43. Iteration 13: 3353997306.250 ops/s
  44. Iteration 14: 3345386706.248 ops/s
  45. Iteration 15: 3098585828.297 ops/s
  46. Iteration 16: 3320139197.980 ops/s
  47. Iteration 17: 3296801550.881 ops/s
  48. Iteration 18: 3355494734.902 ops/s
  49. Iteration 19: 3356800903.223 ops/s
  50. Iteration 20: 3337077281.418 ops/s
  51. # Run progress: 50.00% complete, ETA 00:00:48
  52. # Fork: 2 of 2
  53. # Warmup Iteration 1: 3231242417.192 ops/s
  54. # Warmup Iteration 2: 3325570290.132 ops/s
  55. # Warmup Iteration 3: 3096949615.262 ops/s
  56. # Warmup Iteration 4: 3332459099.679 ops/s
  57. # Warmup Iteration 5: 3271409988.593 ops/s
  58. # Warmup Iteration 6: 3339628843.979 ops/s
  59. # Warmup Iteration 7: 3347272201.167 ops/s
  60. # Warmup Iteration 8: 3326039572.106 ops/s
  61. # Warmup Iteration 9: 3343615974.048 ops/s
  62. # Warmup Iteration 10: 3340006349.379 ops/s
  63. # Warmup Iteration 11: 3350308589.717 ops/s
  64. # Warmup Iteration 12: 3343823064.788 ops/s
  65. # Warmup Iteration 13: 3342492145.958 ops/s
  66. # Warmup Iteration 14: 3359616251.572 ops/s
  67. # Warmup Iteration 15: 3328707715.588 ops/s
  68. # Warmup Iteration 16: 3347526411.075 ops/s
  69. # Warmup Iteration 17: 3332288499.407 ops/s
  70. # Warmup Iteration 18: 3333285221.323 ops/s
  71. # Warmup Iteration 19: 3363231847.951 ops/s
  72. # Warmup Iteration 20: 3324800707.451 ops/s
  73. Iteration 1: 3329347165.195 ops/s
  74. Iteration 2: 3343019432.956 ops/s
  75. Iteration 3: 3359178908.665 ops/s
  76. Iteration 4: 3359706227.777 ops/s
  77. Iteration 5: 3323662154.636 ops/s
  78. Iteration 6: 3342720460.496 ops/s
  79. Iteration 7: 3358126610.714 ops/s
  80. Iteration 8: 3355181834.419 ops/s
  81. Iteration 9: 3304847464.277 ops/s
  82. Iteration 10: 3343412260.638 ops/s
  83. Iteration 11: 3361426396.016 ops/s
  84. Iteration 12: 3358865902.968 ops/s
  85. Iteration 13: 3361451162.560 ops/s
  86. Iteration 14: 3347432510.258 ops/s
  87. Iteration 15: 3344210584.374 ops/s
  88. Iteration 16: 3344063073.737 ops/s
  89. Iteration 17: 3347884645.408 ops/s
  90. Iteration 18: 3336115349.114 ops/s
  91. Iteration 19: 3360040161.433 ops/s
  92. Iteration 20: 3349369254.004 ops/s
  93. Result: 3334462775.852 ±(99.9%) 25009132.058 ops/s [Average]
  94. Statistics: (min, avg, max) = (3098585828.297, 3334462775.852, 3361451162.560), stdev = 44453710.274
  95. Confidence interval (99.9%): [3309453643.794, 3359471907.910]
  96. # Run complete. Total time: 00:01:37
  97. Benchmark Mode Samples Score Score error Units
  98. c.h.MyBenchmark.testMethod thrpt 40 3334462775.852 25009132.058 ops/s
  99. Process finished with exit code 0
  • Fork: 执行线程
  • Warmup Iteration : 预热迭代执行
  • Iteration :正常的迭代执行
  • result : 最后是结果
    • Benchmark : 执行的方法
    • Mode: 基准测试模式
      • Throughput(默认模式): 整体吞吐量,例如“1秒内可以执行多少次调用”。
      • Average Time: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
      • Sample Time:随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
      • Single Shot Time:以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
      • All: 测量以上所有
    • Samples: 表示采样次数
    • Score(最后关心结果): 对这次评测的打分
    • Score error: 这里表示性能统计上的误差
    • Units: 单位 每秒操作

3. 常用注解说明

3.1 @BenchmarkMode

表示 JMH 进行 Benchmark 时所使用的模式。默认的模式是Throughput

  1. @Inherited
  2. @Target({ElementType.METHOD, ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface BenchmarkMode {
  5. /**
  6. * @return Which benchmark modes to use.
  7. * @see Mode
  8. */
  9. Mode[] value();
  10. }

3.2 @OutputTimeUnit

表示JMH 进行Benchmark时,输出结果的时间单位

  1. @Inherited
  2. @Target({ElementType.METHOD,ElementType.TYPE})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface OutputTimeUnit {
  5. /**
  6. * @return Time unit to use.
  7. */
  8. TimeUnit value();
  9. }

支持以下单位

  • NANOSECONDS 纳秒
  • MICROSECONDS 微秒
  • MILLISECONDS 毫秒
  • SECONDS 秒
  • MINUTES 分钟
  • HOURS 小时
  • DAYS 天

3.3 @State

用于标识状态对象,以便作为Benchmark的参数

  1. @Inherited
  2. @Target(ElementType.TYPE)
  3. @Retention(RetentionPolicy.RUNTIME)
  4. public @interface State {
  5. /**
  6. * State scope.
  7. * @return state scope
  8. * @see Scope
  9. */
  10. Scope value(); //状态的共享范围
  11. }
  • Scope
    • Benchmark: 该状态的意思是会在所有的Benchmark的工作线程中共享变量内容。
    • Group : 同一个Group的线程可以享有同样的变量
    • Thread: 每隔线程都享有一份变量的副本,线程之间对于变量的修改不会相互影响
  • State 类定义要求

    • @State修饰的类必须声明为public
    • 如果是内部类,则必须是静态内部类,且声明为public
    • 该类必须有一个声明为public 且无参的构造方法
      3.4  State对象 @Setup 和 @TearDown 注解
  • @Setup 在基准测试之前运行的方法。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Setup {

    /**

    • @return Level of this method.
    • @see Level */ Level value() default Level.Trial;

}

  1. - @TearDown 在基准测试之后运行的方法。
  2. ```java
  3. @Target(ElementType.METHOD)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. public @interface TearDown {
  6. /**
  7. * @return At which level to run this fixture.
  8. * @see Level
  9. */
  10. Level value() default Level.Trial;
  11. }
  • Level

    • Level.Tiral: 运行每个性能测试的时候执行,推荐的方式。
    • Level.Iteration, 每次迭代的时候执行
    • Level.Invocation,每次调用方法的时候执行,这个选项需要谨慎使用。
      3.5 注解控制测试
  • @Fork: 需要运行的试验(迭代集合)数量。每个试验运行在单独的JVM进程中。也可以指定(额外的)JVM参数。

  • @Measurement :提供真正的测试阶段参数。指定迭代的次数,每次迭代的运行时间和每次迭代测试调用的数量(通常使用@BenchmarkMode(Mode.SingleShotTime)测试一组操作的开销——而不使用循环)
  • @Warmup: 与@Measurement相同,但是用于热身阶段
  • @Threads 该测试使用的线程数。默认是Runtime.getRuntime().availableProcessors()

二、例子实践

2.1 StringBuffer vs StringBuilder vs String +拼接

StringBuffer ,StringBuilder 是可变的字符串对象;StringBuffer是线程安全的,StringBuilder是线程不安全的。

  • 代码 ```java @BenchmarkMode(Mode.AverageTime) //平均时间模式测试 @Warmup(iterations = 3, time = 1) //预热3次,每次1秒 @Measurement(iterations = 5, time = 5) //执行5次,每次5秒 @Threads(4) @Fork(1) @State(value = Scope.Benchmark) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class MyStringPerformanceTest {

    /**

    • 测试从100 到 10000次的性能 */ @Param(value = {“100”, “1000”, “10000”}) private int length;
  1. @Benchmark
  2. public void testStringAdd(Blackhole blackhole) {
  3. String a = "";
  4. for (int i = 0; i < length; i++) {
  5. a += i;
  6. }
  7. blackhole.consume(a);
  8. }
  9. @Benchmark
  10. public void testStringBuilderAdd(Blackhole blackhole) {
  11. StringBuilder sb = new StringBuilder();
  12. for (int i = 0; i < length; i++) {
  13. sb.append(i);
  14. }
  15. blackhole.consume(sb.toString());
  16. }
  17. @Benchmark
  18. public void testStringBufferAdd(Blackhole blackhole) {
  19. StringBuffer sb = new StringBuffer();
  20. for (int i = 0; i < length; i++) {
  21. sb.append(i);
  22. }
  23. blackhole.consume(sb.toString());
  24. }
  25. public static void main(String[] args) throws RunnerException {
  26. Options options = new OptionsBuilder()
  27. .include(MyStringPerformanceTest.class.getSimpleName())
  28. .resultFormat(ResultFormatType.JSON)
  29. .result("jmh.json")
  30. .build();
  31. new Runner(options).run();
  32. }

}

  1. - 输出结果
  2. ```shell
  3. Result: 196352.295 ±(99.9%) 16611.499 ns/op [Average]
  4. Statistics: (min, avg, max) = (190627.297, 196352.295, 200791.587), stdev = 4313.954
  5. Confidence interval (99.9%): [179740.796, 212963.794]
  6. # Run complete. Total time: 00:04:34
  7. Benchmark (length) Mode Samples Score Score error Units
  8. c.j.MyStringPerformanceTest.testStringAdd 100 avgt 5 7310.193 828.260 ns/op
  9. c.j.MyStringPerformanceTest.testStringAdd 1000 avgt 5 961887.214 102061.090 ns/op
  10. c.j.MyStringPerformanceTest.testStringAdd 10000 avgt 5 122923980.097 6239607.647 ns/op
  11. c.j.MyStringPerformanceTest.testStringBufferAdd 100 avgt 5 1204.133 67.151 ns/op
  12. c.j.MyStringPerformanceTest.testStringBufferAdd 1000 avgt 5 16443.466 1531.165 ns/op
  13. c.j.MyStringPerformanceTest.testStringBufferAdd 10000 avgt 5 200985.564 30430.347 ns/op
  14. c.j.MyStringPerformanceTest.testStringBuilderAdd 100 avgt 5 1167.801 22.221 ns/op
  15. c.j.MyStringPerformanceTest.testStringBuilderAdd 1000 avgt 5 16287.216 1725.331 ns/op
  16. c.j.MyStringPerformanceTest.testStringBuilderAdd 10000 avgt 5 196352.295 16611.499 ns/op

image.png

三、jmh 编写注意

已经入门了jmh 进行基准测试了, 剩下的只有提高我们的熟练度和书写规范。

3.1 循环优化问题(Loop Optimizations)

3.2 无用代码消除(Dead Code Elimination)

  • 避免无用代码消除方法

    • 在Benchmark 方法中返回结果
    • 使用jmh 提供的Blackhole类消费结果

      3.3 常量折叠(Constant Folding)

  • 什么是常量折叠

常量折叠是

  • 如何避免

避免直接在Benchmark 方法中定义常量,应该使用State 对象

  1. public class MyBenchmark {
  2. @State(Scope.Thread)
  3. public static class MyState {
  4. public int a = 1;
  5. public int b = 2;
  6. }
  7. @Benchmark
  8. public int testMethod(MyState state) {
  9. int sum = state.a + state.b;
  10. return sum;
  11. }
  12. }

四、jmh IDEA插件使用

参考