java基准测试之JMH

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容