JAVA多线程-(二十一)JMH使用

JMH简介

JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级。该工具是由 Oracle 内部实现 JIT 的大牛们编写的,他们应该比任何人都了解 JIT 以及 JVM 对于基准测试的影响。JMH不止能对Java语言做基准测试,还能对运行在JVM上的其他语言做基准测试。

应用场景

  1. 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  2. 对比接口不同实现在给定条件下的吞吐量
  3. 查看多少百分比的请求在多长时间内完成

使用案例

  1. 创建Maven项目,添加依赖
  <dependencies>

        <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->

        <dependency>

            <groupId>org.openjdk.jmh</groupId>

            <artifactId>jmh-core</artifactId>

            <version>1.21</version>

        </dependency>



        <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->

        <dependency>

            <groupId>org.openjdk.jmh</groupId>

            <artifactId>jmh-generator-annprocess</artifactId>

            <version>1.21</version>

            <scope>test</scope>

        </dependency>

    </dependencies>

2.idea安装JMH插件 JMH plugin v1.0.3
3. 由于用到了注解,打开运行程序注解配置

compiler -> Annotation Processors -> Enable Annotation Processing

4.定义需要测试类PS (ParallelStream)

public class PS {
	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(v->isPrime(v));
	}

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

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

5.写单元测试
这个测试类一定要在test package下面

import org.openjdk.jmh.annotations.*;

public class PSTest {
    @Benchmark
    @Warmup(iterations = 1, time = 3)
    @Fork(5)
    @BenchmarkMode(Mode.Throughput)
    @Measurement(iterations = 1, time = 3)
    public void testForEach() {
        PS.parallel();
    }
}
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)

这个错误是因为JMH运行需要访问系统的TMP目录,解决办法是:
打开RunConfiguration -> Environment Variables -> include system environment viables
或者用管理员身份运行idea;

7.阅读测试报告

# JMH version: 1.21
# VM version: JDK 1.8.0_131, Java HotSpot(TM) 64-Bit Server VM, 25.131-b11
# VM invoker: E:\Program Files\Java\jdk1.8.0_131\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 1 iterations, 3 s each
# Measurement: 1 iterations, 3 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.jmh.PSTest.testForEach

该部分为测试的基本信息,比如使用的 Java 路径,预热代码的迭代次数,测量代码的迭代次数,使用的线程数量,测试的统计单位等。

# Warmup Iteration   1: 5.812 ops/s

# Warmup Iteration   1: 5.621 ops/s

# Warmup Iteration   1: 6.625 ops/s

# Warmup Iteration   1: 6.169 ops/s

# Warmup Iteration   1: 6.352 ops/s

该部分为每一次热身中的性能指标,预热测试不会作为最终的统计结果。预热的目的是让 JVM 对被测代码进行足够多的优化,比如,在预热后,被测代码应该得到了充分的 JIT 编译和优化。

Iteration   1: 5.682 ops/s

Iteration   1: 5.786 ops/s

Iteration   1: 6.486 ops/s

Iteration   1: 6.236 ops/s

Iteration   1: 6.390 ops/s

Result "com.jmh.PSTest.testForEach":
  6.116 ±(99.9%) 1.394 ops/s [Average]
  (min, avg, max) = (5.682, 6.116, 6.486), stdev = 0.362
  CI (99.9%): [4.722, 7.509] (assumes normal distribution)

Benchmark             Mode  Cnt   Score   Error  Units
PSTest.testForEach   thrpt    5   6.116 ± 1.394  ops/s

该部分显示测量迭代的情况,每一次迭代都显示了当前的执行速率,即一个操作所花费的时间。

最后的测试结果如下所示:

Benchmark             Mode  Cnt   Score   Error  Units
PSTest.testForEach   thrpt    5   6.116 ± 1.394  ops/s
PSTest.testParallel  thrpt    5  22.008 ± 8.338  ops/s

JMH 基础

@BenchmarkMode
用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行,如:@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),还可以设置为 Mode.All,即全部执行一遍。

Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
SampleTime:随机取样,最后输出取样结果的分布
SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
All:上面的所有模式都执行一次
6.运行测试类,如果遇到下面的错误:
@State
通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
Scope.Group:同一个线程在同一个 group 里共享实例
Scope.Thread:默认的 State,每个测试线程分配一个实例
@OutputTimeUnit
为统计结果的时间单位,可用于类或者方法注解

@Warmup
预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:

iterations:预热的次数
time:每次预热的时间
timeUnit:时间的单位,默认秒
batchSize:批处理大小,每次操作调用几次方法

为什么需要预热?
因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。

@Measurement
实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和 @Warmup 相同。

@Threads
每个进程中的测试线程,可用于类或者方法上。

@Fork
进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。

@Param
指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义 @State 注解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值