zoukankan      html  css  js  c++  java
  • JMH如何使用(一)

    JMH如何使用

    JMH的基本用法

    基础注解 @Benchmark

    在一个基本测试类中至少包含一个被@Benchmark标记的方法,否则会抛出异常。

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @State(Scope.Thread)
    public class JMHExample02 {
    
        public void normalMethod() {
        }
    
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder().include(JMHExample02.class.getSimpleName())
                    .forks(1)
                    .measurementIterations(10)
                    .warmupIterations(10)
                    .build();
            new Runner(opts).run();
        }
    }
    

    异常:Exception in thread "main" No benchmarks to run; check the include/exclude regexps.

    Warmup以及Measurement

    Warmup: "热身",使得在度量之前,类经历了早期的优化、JVM运行期编译、JIT优化

    Measurement: 真正的度量操作,度量过程中的所有数据都会被纳入统计

    全局设置

    1. 构造Options时设置批次执行

      final Options opts = new OptionsBuilder().include(JMHExample02.class.getSimpleName())
                      .forks(1)
         				//度量执行批次为5
                      .measurementIterations(5)
          			//在度量之前先执行两次热身
                      .warmupIterations(2)
                      .build();
              new Runner(opts).run();
      
    2. @Warmup与@Measurement注解

      @BenchmarkMode(Mode.AverageTime)
      @OutputTimeUnit(TimeUnit.MICROSECONDS)
      @State(Scope.Thread)
      
      @Measurement(iterations = 5)
      @Warmup(iterations = 2)
      public class JMHExample02
      

    局部设置(基准测试方法之上)

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @State(Scope.Thread)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    public class JMHExample03 {
    
        @Benchmark
        public void test1() throws InterruptedException {
            TimeUnit.MILLISECONDS.sleep(10);
        }
    
        @Benchmark
        @Warmup(iterations = 5)
        @Measurement(iterations = 4)
        public void test2() throws InterruptedException {
            TimeUnit.MILLISECONDS.sleep(1);
        }
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder().include(JMHExample03.class.getSimpleName())
                    .forks(1)
                	//.measurementIterations(2)
                    //.warmupIterations(2)
                    .build();
            new Runner(opts).run();
        }
    }
    

    执行结果:

    基准测试方法 测试结果
    test1 预热3次、度量5次
    test2 预热5次、度量4次

    Warmup以及Measurement在基准测试方法上的设置会覆盖全局设置,但是无法覆盖Options中构建的全局设置

    BenchmarkMode

    四种模式概念

    模式 使用
    AverageTime 平均响应时间(方法每一次调用)
    Throughput 单位时间内方法的调用次数
    SampleTime 抽样的方式统计性能数据
    SingleShotTime 无论warmup还是measurement,每批次中,基准测试方法只会执行一次(warmup一般设置为0)

    多模式设置

    我们可以在基准测试方法上设置多个模式,甚至是全部

    @BenchmarkMode({Mode.AverageTime,Mode.Throughput})
    @Benchmark
    public void testThroughputAndAverageTime(){
    	TimeUnit.MILLISECONDS.sleep(1);
    }
    @BenchmarkMode(Mode.All)
    @Benchmark
    public void testAll(){
    	TimeUnit.MILLISECONDS.sleep(1);
    }
    

    覆盖次序:基准方法上的设置会覆盖类上的设置,Options上的设置会覆盖所有的设置。

    OutputTimeUnit

    提供了统计结果输出时的时间单位,覆盖次序与BenchmarkMode一致

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    @State(Scope.Thread)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    public class JMHExample04 {
    
        @OutputTimeUnit(TimeUnit.MICROSECONDS)
        @Benchmark
        public void test() throws InterruptedException {
            TimeUnit.SECONDS.sleep(1);
        }
    
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder().include(JMHExample04.class.getSimpleName())
                    .include(JMHExample04.class.getSimpleName())
                    .timeUnit(TimeUnit.NANOSECONDS)
                    .forks(1)
                    .build();
            new Runner(opts).run();
        }
    }
    

    三种State的使用

    Thread独享(共享)的State

    1. Scope.Thread: 每个运行基准测试方法的线程都拥有一个独立的对象实例
    2. Scope.Benchmark: 多个线程共享同一个对象实例
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @Fork(1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    //设置5个线程运行基准测试方法
    @Threads(5)
    public class JMHExample07 {
    
        @State(Scope.Benchmark)
        //@State(Scope.Benchmark)
        public static class Test {
            public Test() {
                System.out.println("create instance");
            }
    
            public void method() {
            }
        }
    
        @Benchmark
        public void test(Test test){
            test.method();
        }
    
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder()
                    .include(JMHExample07.class.getSimpleName())
                    .build();
            new Runner(opts).run();
        }
    }
    

    执行结果

    ​ @State(Scope.Thread)输出了5次 "create instance"

    ​ @State( Scope.Benchmark)只输出了1次 "create instance"

    上述的“对象”是指被@State标记的类实例。可通过基准测试方法的参数引入(如上),或是直接运行基准测试方法所在的宿主class。

    @Thread注解设置参与基准测试的线程数

    线程组共享的State

    1. Scope.Group
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @Fork(1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    //设置5个线程运行基准测试方法
    @Threads(5)
    public class JMHExample08 {
    
        @State(Scope.Group)
        public static class Test {
            public Test() {
                System.out.println("create instance");
            }
    
            public void read() {
                System.out.println("test read");
            }
    
            public void write() {
                System.out.println("test write");
            }
        }
    
        @GroupThreads(3)
        @Group("test")
        @Benchmark
        public void testRead(Test test){
            test.read();
        }
    
        @GroupThreads(3)
        @Group("test")
        @Benchmark
        public void testWrite(Test test){
            test.write();
        }
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder()
                    .include(JMHExample08.class.getSimpleName())
                    .build();
            new Runner(opts).run();
        }
    }
    

    执行结果:testRead()与testWrite()交替执行

    前两种State的情况下,基准测试方法都只能按照顺序逐个执行。而想要多个方法并行地去访问共享数据,则需要Scope.Group

    @Param的使用

    假如我们现在想要对两个不同类型的Map进行微基准的性能测试,该怎么做呢?按照前面的方法,我们可以为两个Map分别编写微基准测试方法,如下:

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @Fork(1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    //设置5个线程运行基准测试方法
    @Threads(5)
    @State(Scope.Benchmark)
    public class JMHExample09 {
    
        private Map<Long, Long> concurrentHashMap;
        private Map<Long, Long> synchronizedMap;
        @Setup
        public void setUp(){
            concurrentHashMap = new ConcurrentHashMap<>();
            synchronizedMap = Collections.synchronizedMap(new HashMap<Long, Long>());
        }
    
        @Benchmark
        public void testConcurrentHashMap(){
            this.concurrentHashMap.put(System.nanoTime(),System.nanoTime());
        }
    
        @Benchmark
        public void testSynchronizedMap(){
            this.concurrentHashMap.put(System.nanoTime(),System.nanoTime());
        }
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder()
                    .include(JMHExample09.class.getSimpleName())
                    .build();
            new Runner(opts).run();
        }
    }
    

    但是当我们想对更多类型的集合(或是其他的东东)进行微基准测试时,这种方法显然就多了很多的冗余代码,此时我们就可以使用@Param来简化代码啦,如下:

    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @Fork(1)
    @Measurement(iterations = 5)
    @Warmup(iterations = 3)
    //设置5个线程运行基准测试方法
    @Threads(5)
    //多个线程共享实例
    @State(Scope.Benchmark)
    public class JMHExample10 {
    
        @Param({"1","2","3","4"})
        private int type;
    
        Map<Object, Object> map = null;
        @Setup
        public void setUp(){
            switch (type){
                case 1:
                    this.map = new ConcurrentHashMap<>();
                    break;
                case 2:
                    this.map = new ConcurrentSkipListMap<>();
                    break;
                case 3:
                    this.map = new Hashtable<>();
                    break;
                case 4:
                    this.map = Collections.synchronizedMap(new HashMap<>());
            }
        }
    
        @Benchmark
        public void test(){
            this.map.put(System.nanoTime(),System.nanoTime());
        }
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder()
                    .include(JMHExample10.class.getSimpleName())
                    .build();
            new Runner(opts).run();
        }
    }
    

    执行结果

    结果中只截取了部分关键输出,多出的type列正是对应@Param所提供的参数

    Benchmark                           (type)  Mode  Cnt       Score        Error  Units
    JMHExample09.testConcurrentHashMap       1  avgt    5   23098.216 ± 161361.562  us/op
    JMHExample09.testConcurrentHashMap       2  avgt    5   45467.394 ± 103183.828  us/op
    JMHExample09.testConcurrentHashMap       3  avgt    5   61373.892 ± 243766.954  us/op
    JMHExample09.testConcurrentHashMap       4  avgt    4  140614.207 ± 650830.876  us/op
    

    各个字段含义

    Mode Cnt Score Error Units type
    模式(四种) 基准方法调用次数 响应时间 时间偏差 时间单位 @Param参数

    有了@Param之后,我们只需要编写一次微基准测试方法即可,JMH会根据@Param提供的参数值自动执行基准测试以及统计。

    JMH的测试套件

    Setup以及TearDown

    1. @Setup: 基准测试方法之前调用,通常用于资源的初始化。
    2. @TearDown: 基准测试方法之后调用,通常用于资源的回收清理工作。
    @BenchmarkMode(Mode.SingleShotTime)
    @Fork(1)
    @Warmup(iterations = 5)
    @Measurement(iterations = 10)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    @State(Scope.Thread)
    public class JMHExample11 {
    
        private List<String> list = null;
        @Setup
        public void setUp(){
            this.list = new ArrayList<>();
            System.out.println("setUp...");
        }
    
        @Benchmark
        public void testRight() {
            this.list.add("Test");
        }
    
        @Benchmark
        public void testWrong() {
            //do nothing here
        }
    
        @TearDown
        public void tearDown() {
            System.out.println("tearDown...");
            assert this.list.size() > 0 : "The Size  Of List Must Lager Than Zero";
        }
    
        public static void main(String[] args) throws RunnerException {
            final Options opts = new OptionsBuilder()
                    .include(JMHExample11.class.getSimpleName())
                    .jvmArgs("-ea") //enable assertion 激活断言
                    .build();
            new Runner(opts).run();
        }
    }
    

    执行结果

    # Warmup Iteration   1: setUp...
    4.900 us/op
    # Warmup Iteration   2: 0.600 us/op
    # Warmup Iteration   3: 0.600 us/op
    # Warmup Iteration   4: 0.400 us/op
    # Warmup Iteration   5: 0.500 us/op
    Iteration   1: 0.600 us/op
    Iteration   2: 0.900 us/op
    Iteration   3: 0.600 us/op
    Iteration   4: 0.400 us/op
    Iteration   5: 0.500 us/op
    Iteration   6: 0.400 us/op
    Iteration   7: 0.300 us/op
    Iteration   8: 0.500 us/op
    Iteration   9: 0.400 us/op
    Iteration  10: tearDown...
    

    使用Level控制测试套件

    通过上面的结果可以知道,默认情况下,@Setup@TearDown的套件方法分别在所有执行批次之前与之后执行。JMH还提供了另外两种配置。

    1. Trial:默认配置。@Setup(Level.Trial)

    2. Iteration:因为可以设置Warmup与Measurement,所以基准方法可能被执行若干个批次。Iteration将允许我们在每个批次前后执行套件方法。

      @Setup(Level.Iteration)
      public void setUp()
      
    3. Invocation:每个批次(Warmup或Measurement)中都可能存在多次基准方法的调用,Invocation将允许我们在每一次调用基准方法的前后执行套件。

      @Setup(Level.Invocation)
      public void setUp()
      

    具体代码不做展示。

  • 相关阅读:
    python基础简记
    关于cmake找不到库的问题
    【STM32H7教程】第92章 STM32H7的FDCAN总线应用之双FDCAN实现(支持经典CAN)
    《安富莱嵌入式周报》第237期:2021.10.25--2021.10.31
    【STM32H7教程】第91章 STM32H7的FDCAN总线基础知识和HAL库API
    【STM32H7教程】第90章 STM32H7的CAN FD总线之关键知识点整理
    【STM32H7教程】第89章 STM32H7的CAN FD总线基础之前世今生
    embOS推出一个RTOS的革命性功能,支持微秒和CPU时钟周期级分辨率的任务调度和API延迟参数设置
    【不是问题的问题】为什么STM32的Flash地址要设置到0x08000000
    《安富莱嵌入式周报》第236期:2021.10.18--2021.10.24
  • 原文地址:https://www.cnblogs.com/randolf/p/15763826.html
Copyright © 2011-2022 走看看