- jmh - samples | GitHub
- Avoiding Benchmarking Pitfalls on the JVM
- Is Protobuf 5x Faster Than JSON? (Part 2)
概念与基本使用
Iteration:JMH 进行测试的最小单位,包含一组 invocationInvocation:一次 benchmark 方法调用
使用 @Benchmark 修饰要测试的方法,就像 JUnit 的 @Test 一样。@BenchmarkMode(Mode[]) 指定从哪些维度做测量和统计。
org.openjdk.jmh.annotations.Mode |
含义 |
|---|---|
Mode.Throughput |
吞吐量,单位时间内能够执行多少次调用(ops/time) |
Mode.AverageTime |
调用方法的平均耗时(time/op) |
Mode.SampleTime |
持续执行一段时间,按特定频率采样获取方法的执行耗时 |
Mode.SingleShotTime |
仅执行一次,通常用于评估冷启动性能 |
Mode.All |
当 JVM 发现某个方法或代码块运行时执行的特别频繁的时候,就会认为这是 Hot Spot Code(热点代码)。JIT 会对热点代码进行优化,因此实际测试前需要预热。使用 Warmup 注解声明预热方式,常用以下参数:
time:每次预热持续的时间timeUnit:时间单位,默认是秒iterations:预热阶段的迭代数
预热阶段不会测量数据,使用 Measurement 描述真正测量阶段的配置,参数及含义与 Warmup 相同。
待测方法需要反复执行多次,但是有一些承载状态的对象是固定不变的。使用 State 注解标记这些对象,其 Scope 属性表示作用范围:
org.openjdk.jmh.annotations.Scope |
作用范围 |
|---|---|
Scope.Benchmark |
变量的作用范围是某个基准测试类 |
Scope.Group |
同一个 Group 里的测试方法共享一个变量实例 |
Scope.Thread |
每个 Thread 拥有独立副本 |
@Setup 和 @TearDown 修饰的方法分别在测试方法运行前后执行,可以用于资源的初始化与释放。这两个注解的 Level 属性标明了方法运行的时机。
org.openjdk.jmh.annotations.Level |
含义 |
|---|---|
Level.Trial |
默认,Benchmark 级别 |
Level.Iteration |
每次迭代都会运行 |
Level.Invocation |
每次方法调用都会运行 |
还有一些配置参数,例如禁止方法内联 @CompilerControl(CompilerControl.Mode.DONT_INLINE) 等,可以参考 jmh - samples | GitHub。
案例
某系统需要开发一个基于用户群组的待办事项清单模块,原理和效果类似使用生产者 - 消费者模型的消息队列,使用时需要进行大量的序列化(写)和更大量的反序列化(读)操作。目前使用的方案是 Jackson,亦有考虑使用 Protobuf。
创建性能测试
快速创建一个 JMH 工程:
创建的测试消息类如下,实际做性能测试时需要根据目标业务设计测试的数据结构,不同方案可能对特定类型的对象有奇效。
Jackson 方案通过 ObjectMapper 实现对象和字符串之间的转换。Protobuf 方案需要编写 .proto 定义文件,通过 protoc 生成 Message 和 Builder 类的 Java 代码,编写 Helper 函数实现 POJO 和 Message 间的转换。
使用 mvn clean verify 产生 benchmarks.jar 进行性能测试。
机器在运行时可能会受到多种因素影响,因此测试结果应关注相对值。在本次测试中使用 Protobuf 进行对象的反序列化,性能大约是 Jackson 方案的 3.3 倍。
对这方面有兴趣的话还可以尝试如下方案: