前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JMH实践-代码性能测试工具

JMH实践-代码性能测试工具

作者头像
老梁
发布2019-09-10 18:08:48
1.7K0
发布2019-09-10 18:08:48
举报

概述

  1. JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件
  2. JMH比较典型的应用场景有:
    • 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;
    • 对比接口不同实现在给定条件下的吞吐量;
    • 查看多少百分比的请求在多长时间内完成;

基本概念

  1. 模式
    • Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
    • AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
    • SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
    • SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。
  2. Iteration Iteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。
  3. Warmup Warmup 是指在实际进行 benchmark 前先进行预热的行为。为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。为了让 benchmark 的结果更加接近真实情况就需要进行预热。

注解说明

  1. @BenchmarkMode 对应Mode选项,可用于类或者方法上, 需要注意的是,这个注解的value是一个数组,可以把几种Mode集合在一起执行,还可以设置为Mode.All,即全部执行一遍。
  2. @State 类注解,JMH测试类必须使用@State注解,State定义了一个类实例的生命周期,可以类比Spring Bean的Scope。由于JMH允许多线程同时执行测试,不同的选项含义如下:
    • Scope.Thread:默认的State,每个测试线程分配一个实例;
    • Scope.Benchmark:所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能;
    • Scope.Group:每个线程组共享一个实例;
  3. @OutputTimeUnit benchmark 结果所使用的时间单位,可用于类或者方法注解,使用java.util.concurrent.TimeUnit中的标准时间单位。
  4. @Benchmark 方法注解,表示该方法是需要进行 benchmark 的对象。
  5. @Setup 方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
  6. @TearDown 方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。
  7. @Param 成员注解,可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。@Param注解接收一个String数组,在@setup方法执行前转化为为对应的数据类型。多个@Param注解的成员之间是乘积关系,譬如有两个用@Param注解的字段,第一个有5个值,第二个字段有2个值,那么每个测试方法会跑5*2=10次。

@Param注解例子

代码语言:javascript
复制
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class JMHSample_27_Params {
    /**
     * In many cases, the experiments require walking the configuration space
     * for a benchmark. This is needed for additional control, or investigating
     * how the workload performance changes with different settings.
     */
    @Param({"1", "31", "65", "101", "103"})
    public int arg;
    @Param({"0", "1", "2", "4", "8", "16", "32"})
    public int certainty;
    @Benchmark
    public boolean bench() {
        return BigInteger.valueOf(arg).isProbablePrime(certainty);
    }
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JMHSample_27_Params.class.getSimpleName())
//                .param("arg", "41", "42") // Use this to selectively constrain/override parameters
                .build();
        new Runner(opt).run();
    }
}

常用选项说明

  1. include benchmark 所在的类的名字,这里可以使用正则表达式对所有类进行匹配。
  2. fork JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个@Benchmark方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。fork选项也可以通过方法注解以及启动参数来设置
  3. warmupIterations 预热的迭代次数,默认1秒。
  4. measurementIterations 实际测量的迭代次数,默认1秒。
  5. CompilerControl 可以在@Benchmark注解中指定编译器行为。
  6. Group 方法注解,可以把多个 benchmark 定义为同一个 group,则它们会被同时执行,譬如用来模拟生产者-消费者读写速度不一致情况下的表现。可以参考如下例子: https://github.com/chrishantha/microbenchmarks/blob/v0.0.1-initial-counter-impl/counters/src/main/java/com/github/chrishantha/microbenchmark/counter/CounterBenchmark.java
  7. Level 用于控制 @Setup,@TearDown 的调用时机,默认是 Level.Trial。
    • Trial:每个benchmark方法前后;
    • Iteration:每个benchmark方法每次迭代前后;
    • Invocation:每个benchmark方法每次调用前后,谨慎使用,需留意javadoc注释;
  8. Threads 每个fork进程使用多少条线程去执行你的测试方法,默认值是Runtime.getRuntime().availableProcessors()。

配置

代码语言:javascript
复制
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.20</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.20</version>
</dependency>

//放到plugins中

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>2.0</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <finalName>microbenchmarks</finalName>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                        <mainClass>org.openjdk.jmh.Main</mainClass>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

代码

代码语言:javascript
复制
package com.beikbank.settlement.jms;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) // 测试方法平均执行时间
@OutputTimeUnit(TimeUnit.MICROSECONDS) // 输出结果的时间粒度为微秒
@State(Scope.Thread) // 每个测试线程一个实例
public class FirstBenchMark {
    private static Logger log = LoggerFactory.getLogger(FirstBenchMark.class);

    @Benchmark
    public String stringConcat() {
        String a = "a";
        String b = "b";
        String c = "c";
        String s = a + b + c;
        log.debug(s);
        return s;
    }
    public static void main(String[] args) throws RunnerException {
        // 使用一个单独进程执行测试,执行5遍warmup,然后执行5遍测试
        Options opt = new OptionsBuilder().include(FirstBenchMark.class.getSimpleName()).forks(1).warmupIterations(5)
                .measurementIterations(5).build();
        new Runner(opt).run();
    }
}

结果

代码语言:javascript
复制
...
16:05:49.342 [com.beikbank.settlement.jms.FirstBenchMark.stringConcat-jmh-worker-1] DEBUG com.beikbank.settlement.jms.FirstBenchMark - abc
16:05:49.342 [com.beikbank.settlement.jms.FirstBenchMark.stringConcat-jmh-worker-1] DEBUG com.beikbank.settlement.jms.FirstBenchMark - abc
75.525 us/op


Result "com.beikbank.settlement.jms.FirstBenchMark.stringConcat":
  71.340 ±(99.9%) 12.417 us/op [Average]
  (min, avg, max) = (68.386, 71.340, 75.525), stdev = 3.225
  CI (99.9%): [58.923, 83.757] (assumes normal distribution)


# Run complete. Total time: 00:00:14

Benchmark                    Mode  Cnt   Score    Error  Units
FirstBenchMark.stringConcat  avgt    5  71.340 ± 12.417  us/op

可以看出此方法运行时间在71微秒正负12微秒之间

参考 https://blog.csdn.net/lxbjkben/article/details/79410740

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-12-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 基本概念
  • 注解说明
  • @Param注解例子
  • 常用选项说明
  • 配置
  • 代码
  • 结果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档