什么是Benchmark?
Benchmark是一个评价方式,在整个计算机领域有着长期的应用。Benchmark在计算机领域应用最成功的就是性能测试,主要测试负载的执行时间、传输速度、吞吐量、资源占用率等。像Redis就有自己的基准测试工具redis-benchmark。
什么是JMH?
JMH (the Java Microbenchmark Harness) ,它被作为Java9的一部分来发布,但是我们完全不需要等待Java9,而可以方便的使用它来简化我们测试,它能够照看好JVM的预热、代码优化,让你的测试过程变得更加简单。
由于jvm底层不断的升级,随着代码执行次数的增多,jvm会不断的进行编译优化,导致要执行很多次才能得出稳定的数据.故我们需要频繁的编写"预热"代码,然后还需要不厌其烦的打印出测试结果。幸好,我们有JMH!使我们的基础测试变得很简单了。
导入JMH依赖
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
</dependency>
创建Demo:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class Helloworld {
@Benchmark
public void firstBenchmark() {
}
}
BenchmarkMode
- Throughput
每段时间执行的次数,一般是秒 - AverageTime
平均时间,每次操作的平均耗时 - SampleTime
在测试中,随机进行采样执行的时间 - SingleShotTime
在每次执行中计算耗时 - All
所有模式,这个在内部测试中常用
State
- Benchmark
同一个benchmark在多个线程之间共享实例 - Group
同一个线程在同一个group里共享实例。group定义参考注解 @Group - Thread
不同线程之间的实例不共享
启动基准测试:
public class HelloworldRunner {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include("Helloworld")
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.forks(1)
.build();
new Runner(opt).run();
}
}
include("Helloworld")
是指定要执行基准测试的目标,可以传入正则。
warmupIterations(10)
在执行前预热10次。
warmupTime(TimeValue.seconds(1))
每一次预热1秒.
measurementIterations(10)
重复执行10次,
measurementTime(TimeValue.seconds(1))
每一次执行1秒。
forks(1)
指的只做1轮测试,为了达到更加准确的效果,可以适当增大该值。
输出基准测试结果:
Result "com.pingan.jmh.HelloWorld.firstBenchmark":
2703833258.555 ±(99.9%) 354675008.250 ops/s [Average]
(min, avg, max) = (2157247993.082, 2703833258.555, 2894733254.695), stdev = 234595557.867
CI (99.9%): [2349158250.305, 3058508266.805] (assumes normal distribution)
# Run complete. Total time: 00:00:21
Benchmark Mode Cnt Score Error Units
HelloWorld.firstBenchmark thrpt 10 2703833258.555 ± 354675008.250 ops/s
Benchmark:基准测试名称
Mode:基础测试模式(这里指吞吐量)
Cnt:次数
Score:这里指每秒执行的次数,当Mode改变的时候,Score含义不同。
Units:单位,这里指每秒执行的次数
实战
我们都知道Apache的BeanUtils.copyProperties()表现不是很好,所以这里我们使用jmh测试一下和PropertyUtils的差异。
直接上代码:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class BeanCopyPropsBenchMark {
private User user;
private Person person;
@Setup
public void init(){
user = new User(3,"jerrik",27,"深圳");
person = new Person();
}
@Benchmark
public Person runBeanUtils() {
try {
BeanUtils.copyProperties(user,person);
return person;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Benchmark
public Person runPropertyUtils() {
try {
PropertyUtils.copyProperties(person,user);
return person;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(BeanCopyPropsBenchMark.class.getSimpleName())
.forks(1)
.threads(1)
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.build();
new Runner(options).run();
}
}
结果:
Result "com.pingan.jmh.BeanCopyPropsBenchMark.runPropertyUtils":
401720.952 ±(99.9%) 41189.586 ops/s [Average]
(min, avg, max) = (325423.974, 401720.952, 417070.687), stdev = 27244.361
CI (99.9%): [360531.367, 442910.538] (assumes normal distribution)
# Run complete. Total time: 00:00:42
Benchmark Mode Cnt Score Error Units
BeanCopyPropsBenchMark.runBeanUtils thrpt 10 55708.695 ± 9226.542 ops/s
BeanCopyPropsBenchMark.runPropertyUtils thrpt 10 401720.952 ± 41189.586 ops/s
PropertyUtils一秒内的执行次数为401720.952 ± 41189.586,而BeanUtils的才55708.695 ± 9226.542,执行效率相差快8倍。可以看出PropertyUtils的性能是远高于BeanUtils的。
更深一层-避免JIT优化
我们在测试的时候,一定要避免JIT优化。对于有一些代码,编译器可以推导出一些计算是多余的,并且完全消除它们。 如果我们的基准测试里有部分代码被清除了,那测试的结果就不准确了:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class BenchmarkWithoutJit {
double x = Math.PI;
@Benchmark
public void withJIT(){
Math.log(x);
}
@Benchmark
public void withoutJIT(Blackhole blackhole){
blackhole.consume(Math.log(x));//consume()可以避免JIT的优化
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include("BenchmarkWithoutJit")
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.forks(1)
.build();
new Runner(opt).run();
}
}
//output
Benchmark Mode Cnt Score Error Units
BenchmarkWithoutJit.withJIT thrpt 10 2509923125.204 ± 430839988.021 ops/s
BenchmarkWithoutJit.withoutJIT thrpt 10 36900419.444 ± 778621.522 ops/s
可知使用JIT优化的情况下,性能要高出很多倍。
根据JMH联想到的应用场景
比如各种序列化机制的速率对比,cas和加锁的性能对比,反射和getter,setter的性能对比,集合框架的性能对比等等。
其他具体更多高级用法可以参考jmh官网的Demo【http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/】