JMH(Java Microbenchmark Harness),作为Java中一款优秀的微基准测试框架。其可以很方便地进行方法级的基准测试,便于开发者更好的测试、验证方法的性能
依赖
JMH的POM依赖如下所示
1 | <dependencies> |
实践
事实上对于JMH而言,其还是很容易上手的。下面通过两个例子来帮助大家快速入门上手
Demo1
这里通过一个Demo来入门JMH的基本使用,示例程序如下所示
1 | ( Mode.AverageTime ) |
其中,涉及到的相关注解说明如下
- @BenchmarkMode:指定基准测试的模式。具体地有
- AverageTime:方法调用的平均时间
- Throughput:吞吐量模式,即每秒调用的次数
- SampleTime:采样模式,即方法调用的耗时分布
- SingleShotTime:上述模式在一次Iteration的时间内会调用方法。而在该模式中一次Iteration仅会调用一次方法。与此同时也会将Warmup预热次数设为0,用于测试方法冷启动时的耗时
- All:其会分别使用上述四种模式进行测试
- @Warmup, @Measurement:设置预热、测试的次数及每次的持续时间
- @Benchmark:标识这是一个需要测试的方法
测试结果如下所示,helloWorld方法平均耗时703ms,可以看到测试结果与我们在helloWorld中的700ms延时基本吻合
对于Throughput吞吐量模式而言,其测试的是1秒内方法平均调用次数。显然其与AverageTime平均时间模式而言,是如出一辙的。故这里就不进行赘述了。这里重点对于SampleTime采样模式的输出结果进行介绍,示例如下
1 | // 测试信息 |
Demo2
现在我们通过SingleShotTime模式测试下方法的冷启动性能。示例代码如下所示
1 | import org.openjdk.jmh.annotations.Param; |
这里我们通过@Param注解实现测试多个不同的参数条件。测试结果的部分内容如下所示
1 | // 测试过程 |
Note
DCE 死码消除
DCE(Dead code elimination)死码消除,作为编译器中一项常见的优化技术。当我们进行基准测试时,稍有不慎即有可能影响我们的测试结果。下面是一个示例程序,用于测试Math.log方法的性能
1 | /** |
可以看到,我们使用了三种形式(test1、test2、test3)来进行测试,测试结果如下所示
可以发现,当我们使用test1的形式进行测试,发现其测试结果明显偏低,几乎等同于一个空方法的耗时。原因就在于我们未显式地使用该计算结果,被检测为死码优化掉了。为了避免该问题一方面我们可以通过return语句来显式的使用,另一方面JMH提供了一个blackhole.consume方法以供对返回结果进行消费来避免发生DCE
常量折叠
所谓常量折叠值得的是,编译器会在编译期间就完成常量间的计算并将结果存储下来,而不再保留常量的计算表达式。对于Java而言,使用final修饰的变量即会在编译期被特殊处理
1 | /** |
测试结果如下所示,可以看到只有test3的测试结果才是可靠的