java基准测试之JMH

作者: jerrik | 来源:发表于2018-07-10 00:48 被阅读17次

什么是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/

相关文章

网友评论

    本文标题:java基准测试之JMH

    本文链接:https://www.haomeiwen.com/subject/nxyxpftx.html