JUC学习笔记 - 08JMH入门

406 阅读2分钟

写在前面: JMH指的是Java Microbenchmark Harness即为基准测试。本文简单介绍JMH的使用,只是作为一个入门,更详细的介绍最好参考官网:openjdk.java.net/projects/co…

JMH测试项目创建

  1. 添加依赖jmh-corejmh-generator-annprocess
  <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>
      <scope>test</scope>
  </dependency>
  1. 安装idea安装插件:

JMH插件.jpg

  1. 允许JMH能够怼注解进行处理

compiler -> Annotation Processors -> Enable Annotation Processing

  1. 定义测试类
public class TestParallelStream {

    static List<Integer> nums = new ArrayList<>();

    static {
        Random r = new Random();
        for (int i = 0; i < 1000; i++) {
            nums.add(1000000 + r.nextInt(1000000));
        }
    }

    static void foreach() {
        nums.forEach(TestParallelStream::isPrime);
    }

    static void parallel() {
        nums.parallelStream().forEach(TestParallelStream::isPrime);
    }

    static boolean isPrime(int num) {
        for (int i = 2; i <= num / 2; i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }
}
`

5. 单元测试

```java
    @Benchmark
    @Warmup(iterations = 5, time = 3)
    @Fork(5)
    @BenchmarkMode(Mode.Throughput)
    @Measurement(iterations = 5, time = 3)
    public void testParallel() {
        TestParallelStream.parallel();
    }
  • @Warmup(iterations = 5, time = 3):表示调用5次,每次调用间隔3秒。预热后期望由动态编译转变为JIT,这样可以更好地模拟线上环境;
  • @Fork(5):5个线程去执行程序;
  • @BenchmarkMode(Mode.Throughput):基准测试模式,此处为吞吐量模式。该配置模式如下:
    • Throughput:吞吐量,即QPS或日志中的ops/time
    • AverageTime:平均调用时间,即每次调用平均耗时
    • SampleTime:随机取样,最后输出取样结果分布。例如百分之多少的调用再多少毫秒内完成等等;
    • SingleShotTime:以上模式都是默认一次iteration是1s,唯有SingleShotTime是只运行一次,往往同时把@Warmup次数设为0,用于测试冷启动时的性能。
  • @Measurement(iterations = 5, time = 3):整个测试需要测试5次,每次间隔3秒;
  1. Windows系统注意事项

老版本的idea运行单元测试时可能会出现如下错误:

ERROR: org.openjdk.jmh.runner.RunnerException: ERROR: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock): C:\WINDOWS\jmh.lock (拒绝访问。), exiting. Use -Djmh.ignoreLock=true to forcefully continue.
at org.openjdk.jmh.runner.Runner.run(Runner.java:216)
at org.openjdk.jmh.Main.main(Main.java:71)

需要通过如下配置:

RunConfiguration -> Environment Variables -> include system environment viables

include system environment viables.png

阅读测试报告

# JMH version: 1.21
# VM version: JDK 11.0.13, Java HotSpot(TM) 64-Bit Server VM, 11.0.13+10-LTS-370
# VM invoker: D:\Work\Program\java11\bin\java.exe
# VM options: -javaagent:D:\Work\Program\idea\IntelliJ IDEA 2021.2.2\lib\idea_rt.jar=63840:D:\Work\Program\idea\IntelliJ IDEA 2021.2.2\bin -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 3 s each // 预热5次,每次间隔3秒
# Measurement: 5 iterations, 3 s each // 测试5次,每次间隔3秒
# Timeout: 10 min per iteration // 每次运行需要在10分钟以内
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time // 测试模式为吞吐量
# Benchmark: org.wzy.jmh.ParallelTest.testParallel // 测试代码

# Run progress: 0.00% complete, ETA 00:02:30 // 第一个线程
# Fork: 1 of 5
// 预热时吞吐量
# Warmup Iteration   1: 61.897 ops/s
# Warmup Iteration   2: 61.725 ops/s
# Warmup Iteration   3: 61.163 ops/s
# Warmup Iteration   4: 61.048 ops/s
# Warmup Iteration   5: 62.912 ops/s
// 测试时吞吐量
Iteration   1: 64.143 ops/s
Iteration   2: 62.319 ops/s
Iteration   3: 63.610 ops/s
Iteration   4: 63.671 ops/s
Iteration   5: 63.535 ops/s

# Run progress: 20.00% complete, ETA 00:02:03 // 第二个线程
# Fork: 2 of 5
# Warmup Iteration   1: 76.065 ops/s
# Warmup Iteration   2: 76.243 ops/s
# Warmup Iteration   3: 76.381 ops/s
# Warmup Iteration   4: 76.604 ops/s
# Warmup Iteration   5: 75.955 ops/s
Iteration   1: 75.314 ops/s
Iteration   2: 76.458 ops/s
Iteration   3: 76.929 ops/s
Iteration   4: 76.739 ops/s
Iteration   5: 76.487 ops/s

# Run progress: 40.00% complete, ETA 00:01:32 // 第三个线程
# Fork: 3 of 5
# Warmup Iteration   1: 70.835 ops/s
# Warmup Iteration   2: 71.576 ops/s
# Warmup Iteration   3: 70.918 ops/s
# Warmup Iteration   4: 68.401 ops/s
# Warmup Iteration   5: 67.818 ops/s
Iteration   1: 71.080 ops/s
Iteration   2: 68.981 ops/s
Iteration   3: 68.563 ops/s
Iteration   4: 69.042 ops/s
Iteration   5: 68.757 ops/s

# Run progress: 60.00% complete, ETA 00:01:01 // 第四个线程
# Fork: 4 of 5
# Warmup Iteration   1: 69.396 ops/s
# Warmup Iteration   2: 70.910 ops/s
# Warmup Iteration   3: 70.832 ops/s
# Warmup Iteration   4: 70.124 ops/s
# Warmup Iteration   5: 69.749 ops/s
Iteration   1: 70.402 ops/s
Iteration   2: 70.044 ops/s
Iteration   3: 70.150 ops/s
Iteration   4: 69.973 ops/s
Iteration   5: 70.583 ops/s

# Run progress: 80.00% complete, ETA 00:00:30 // 第五个线程
# Fork: 5 of 5
# Warmup Iteration   1: 60.714 ops/s
# Warmup Iteration   2: 60.338 ops/s
# Warmup Iteration   3: 59.768 ops/s
# Warmup Iteration   4: 59.520 ops/s
# Warmup Iteration   5: 58.724 ops/s
Iteration   1: 59.783 ops/s
Iteration   2: 59.664 ops/s
Iteration   3: 59.773 ops/s
Iteration   4: 59.951 ops/s
Iteration   5: 60.768 ops/s


Result "org.wzy.jmh.ParallelTest.testParallel":
  67.869 ±(99.9%) 4.371 ops/s [Average]
  (min, avg, max) = (59.664, 67.869, 76.929), stdev = 5.835
  CI (99.9%): [63.498, 72.240] (assumes normal distribution)


# Run complete. Total time: 00:02:34

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
ParallelTest.testParallel  thrpt   25  67.869 ± 4.371  ops/s

Process finished with exit code 0

本文只是一个JMH的基础入门,官网有更详细的案例可供参考: hg.openjdk.java.net/code-tools/…