• 使用JMH微基准测试


    概述

    JMH只适合细粒度的方法测试,并不适用于系统之间的链路测试

    使用Maven搭建基准测试项目骨架

    JMH官方推荐使用Maven来搭建基准测试的骨架,使用也很简单,使用如下命令来生成maven项目:

    mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=org.sample -DartifactId=jmh-benchmark -Dversion=1.0
    

    上面的maven命令使用了jmh-java-benchmark-archetype来生成java语言的基准测试骨架,如果使用其他语言可以将这个参数对应替换,所有可选参数参考jmh,生成的项目groupId是org.sample,artifaceId是test,执行完之后会在当前目录下生成一个test目录,切换到test目录下执行mvn clean install就会生成benchmarks.jar,再使用java -jar benchmarks.jar就可以执行基准测试了。

    JMH参数配置

    如果你想直接在已有maven项目中集成JMH,那也很简单,手动在POM文件中添加以下两个依赖就行了,

          <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.26</version>
          </dependency>
    
          <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.26</version>
            <scope>provided</scope>
          </dependency>
    

    从maven archetype插件生成的pom文件来看,这个工程使用了maven-shade-plugin来将所有的依赖打包到同一个jar包中,并在Manifest文件中配置了Main-Class属性,这样就能直接通过java -jar命令来执行了。

    注解说明

    @Benchmark

    @Benchmark标签是用来标记测试方法的,只有被这个注解标记的话,该方法才会参与基准测试,但是有一个基本的原则就是被@Benchmark标记的方法必须是public的。

    @Warmup

    @Warmup用来配置预热的内容,可用于类或者方法上,越靠近执行方法的地方越准确。一般配置warmup的参数有这些:

    • iterations:预热的次数。
    • time:每次预热的时间。
    • timeUnit:时间单位,默认是s。
    • batchSize:批处理大小,每次操作调用几次方法。(后面用到)

    @Measurement

    用来控制实际执行的内容,配置的选项本warmup一样。

    @BenchmarkMode

    @BenchmarkMode主要是表示测量的纬度,有以下这些纬度可供选择:

    • Mode.Throughput 吞吐量纬度
    • Mode.AverageTime 平均时间
    • Mode.SampleTime 抽样检测
    • Mode.SingleShotTime 检测一次调用
    • Mode.All 运用所有的检测模式 在方法级别指定@BenchmarkMode的时候可以一定指定多个纬度,例如: @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime}),代表同时在多个纬度对目标方法进行测量。

    @OutputTimeUnit

    @OutputTimeUnit代表测量的单位,比如秒级别,毫秒级别,微妙级别等等。一般都使用微妙和毫秒级别的稍微多一点。该注解可以用在方法级别和类级别,当用在类级别的时候会被更加精确的方法级别的注解覆盖,原则就是离目标更近的注解更容易生效。

    @State

    在很多时候我们需要维护一些状态内容,比如在多线程的时候我们会维护一个共享的状态,这个状态值可能会在每隔线程中都一样,也有可能是每个线程都有自己的状态,JMH为我们提供了状态的支持。该注解只能用来标注在类上,因为类作为一个属性的载体。 @State的状态值主要有以下几种:

    • Scope.Benchmark 该状态的意思是会在所有的Benchmark的工作线程中共享变量内容。
    • Scope.Group 同一个Group的线程可以享有同样的变量
    • Scope.Thread 每隔线程都享有一份变量的副本,线程之间对于变量的修改不会相互影响。 下面看两个常见的@State的写法:

    容器测试

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.TimeUnit;
    import org.openjdk.jmh.annotations.Benchmark;
    import org.openjdk.jmh.annotations.BenchmarkMode;
    import org.openjdk.jmh.annotations.Measurement;
    import org.openjdk.jmh.annotations.Mode;
    import org.openjdk.jmh.annotations.OutputTimeUnit;
    import org.openjdk.jmh.annotations.Warmup;
    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;
    
    /**
     * @copyright: Copyright (c) 2020
     * @company: 上海汇石数字科技有限公司
     * @description:
     * @author: Qi.Hong
     * @create: 2020-10-13 12:15
     */
    
    @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
    @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
    public class CollectionsTest {
    	private static final int TEN_MILLION = 10000000;
    	@Benchmark
    	@BenchmarkMode(Mode.AverageTime)
    	@OutputTimeUnit(TimeUnit.MICROSECONDS)
    	public void arrayList() {
    		List<String> array = new ArrayList<>();
    		for (int i = 0; i < TEN_MILLION; i++) {
    			array.add("123");
    		}
    	}
    	@Benchmark
    	@BenchmarkMode(Mode.AverageTime)
    	@OutputTimeUnit(TimeUnit.MICROSECONDS)
    	public void arrayListSize() {
    		List<String> array = new ArrayList<>(TEN_MILLION);
    		for (int i = 0; i < TEN_MILLION; i++) {
    			array.add("123");
    		}
    	}
    	public static void main(String[] args) throws RunnerException {
    		Options opt = new OptionsBuilder()
    				.include(CollectionsTest.class.getSimpleName())
    				.forks(1)
    				.build();
    		new Runner(opt).run();
    	}
    }
    

    输出:

    # JMH version: 1.26
    # VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/bin/java
    # VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50693:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 1 thread, will synchronize iterations
    # Benchmark mode: Average time, time/op
    # Benchmark: org.sample.CollectionsTest.arrayList
    
    # Run progress: 0.00% complete, ETA 00:00:20
    # Fork: 1 of 1
    # Warmup Iteration   1: 80599.837 us/op
    # Warmup Iteration   2: 71816.276 us/op
    # Warmup Iteration   3: 84441.683 us/op
    # Warmup Iteration   4: 50829.796 us/op
    # Warmup Iteration   5: 55140.199 us/op
    Iteration   1: 47282.917 us/op
    Iteration   2: 46334.255 us/op
    Iteration   3: 46759.845 us/op
    Iteration   4: 51672.301 us/op
    Iteration   5: 47182.892 us/op
    
    
    Result "org.sample.CollectionsTest.arrayList":
      47846.442 ±(99.9%) 8361.853 us/op [Average]
      (min, avg, max) = (46334.255, 47846.442, 51672.301), stdev = 2171.547
      CI (99.9%): [39484.589, 56208.295] (assumes normal distribution)
    
    
    # JMH version: 1.26
    # VM version: JDK 1.8.0_231, Java HotSpot(TM) 64-Bit Server VM, 25.231-b11
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/bin/java
    # VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50693:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 1 thread, will synchronize iterations
    # Benchmark mode: Average time, time/op
    # Benchmark: org.sample.CollectionsTest.arrayListSize
    
    # Run progress: 50.00% complete, ETA 00:00:11
    # Fork: 1 of 1
    # Warmup Iteration   1: 27515.066 us/op
    # Warmup Iteration   2: 24900.710 us/op
    # Warmup Iteration   3: 25903.686 us/op
    # Warmup Iteration   4: 25160.727 us/op
    # Warmup Iteration   5: 24623.791 us/op
    Iteration   1: 30331.259 us/op
    Iteration   2: 34517.041 us/op
    Iteration   3: 34419.362 us/op
    Iteration   4: 34507.659 us/op
    Iteration   5: 34385.104 us/op
    
    
    Result "org.sample.CollectionsTest.arrayListSize":
      33632.085 ±(99.9%) 7108.603 us/op [Average]
      (min, avg, max) = (30331.259, 33632.085, 34517.041), stdev = 1846.082
      CI (99.9%): [26523.482, 40740.688] (assumes normal distribution)
    
    
    # Run complete. Total time: 00:00:22
    
    REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
    why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
    experiments, perform baseline and negative tests that provide experimental control, make sure
    the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
    Do not assume the numbers tell you what you want them to tell.
    
    Benchmark                      Mode  Cnt      Score      Error  Units
    CollectionsTest.arrayList      avgt    5  47846.442 ± 8361.853  us/op
    CollectionsTest.arrayListSize  avgt    5  33632.085 ± 7108.603  us/op
    
    Process finished with exit code 0
    

    参考

    使用JMH微基准测试一切

    基于Maven的JMH的搭建和使用

    常见的集合容器应当避免的坑

    JMH官方案例

    基准测试神器-JMH

  • 相关阅读:
    mysql 从库执行insert失败导致同步停止
    MySQL 占用cpu 100%
    MySQl 主从配置实战
    tomcat 线程数与 mysql 连接数综合调优
    mysql 数据迁移
    Windows系统上设置 Git Bash 的 Font 及 Locale
    java -jar 使用要点
    ConcurrentHashMap 从Java7 到 Java8的改变
    sql索引组织
    注册、启动、停止windows服务
  • 原文地址:https://www.cnblogs.com/hongdada/p/13809962.html
Copyright © 2020-2023  润新知