JMH-Java性能测试

1、为什么需要JMH

  • 时间精度问题,本身获取到的时间戳就是存在误差的,它和操作系统有关。
  • JVM 在运行时会进行代码预热,说白了就是越跑越快。因为类需要装载、需要准备操作。
  • JVM 会在各个阶段都有可能对你的代码进行优化处理。
  • 资源回收的不确定性,可能运行很快,回收很慢。

2、JMH是什么

JMH 的全名是Java Microbenchmark Harness,它是由 Java 虚拟机团队开发的一款用于 Java 微基准测试工具。

使用 JMH 可以让你方便快速的进行一次严格的代码基准测试,并且有多种测试模式,多种测试维度可供选择;而且使用简单、增加注解便可启动测试。

3、JMH 使用

3.1 引入依赖

		<!--jmh 基准测试 -->
		 <properties>
       		 <jmh.version>1.0</jmh.version>
    	</properties>
  	
  		<dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>${jmh.version}</version>
        </dependency>
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>${jmh.version}</version>
            <scope>provided</scope>
        </dependency>

3.2 测试案例

3.2.1 字符串拼接
@BenchmarkMode(Mode.AverageTime)
@State(Scope.Thread)
@Fork(1)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
public class MyBenchmark{

    @Benchmark
    public String measureStringApend() {
        String targetString = "";
        for (int i = 0; i < 10000; i++) {
            targetString += "hello";
        }
        return targetString;
    }
    @Benchmark
    public String measureStringBufferApend() {
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < 10000; i++) {
            buffer.append("hello");
        }
        return buffer.toString();
    }
    @Benchmark
    public String measureStringBuilderApend() {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append("hello");
        }
        return builder.toString();
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .build();
        // 在参数方式配置测试
		/*  Options opt = new OptionsBuilder()
                .include(MyBenchmark.class.getSimpleName())
                .forks(1)
                .warmupIterations(3)
                .measurementIterations(5)
                .build();*/
        new Runner(opt).run();
    }
}
  • 测试结果如下:


# Warmup: 3 iterations, 1 s each // 预热运行三次
# Measurement: 5 iterations, 1 s each // 性能测试5次 
# Threads: 1 thread, will synchronize iterations // 线程数量为1
# Benchmark mode: Average time, time/op  // 统计方法调用一次的平均时间
# Benchmark: com.example.demo1.quality.MyBenchmark.measureStringApend // 本次执行的方法

// 1、measureStringApend 
# Run progress: 0.00% complete, ETA 00:00:24
# Fork: 1 of 1
# Warmup Iteration   1: 504372.433 us/op  // 第一次预热,耗时95us
# Warmup Iteration   2: 233214.700 us/op
# Warmup Iteration   3: 140691.413 us/op
Iteration   1: 75023.720 us/op
Iteration   2: 64375.283 us/op
Iteration   3: 67601.253 us/op
Iteration   4: 72401.631 us/op
Iteration   5: 63729.739 us/op


Result: 68626.325 ±(99.9%) 19086.757 us/op [Average]
  // 执行的最小、平均、最大、误差值
  Statistics: (min, avg, max) = (63729.739, 68626.325, 75023.720), stdev = 4956.770
  Confidence interval (99.9%): [49539.568, 87713.082]


// 2、measureStringBufferApend
# Run progress: 33.33% complete, ETA 00:00:22
# Fork: 1 of 1
# Warmup Iteration   1: 145.465 us/op
# Warmup Iteration   2: 121.062 us/op
# Warmup Iteration   3: 106.924 us/op
Iteration   1: 99.278 us/op
Iteration   2: 101.873 us/op
Iteration   3: 98.889 us/op
Iteration   4: 100.907 us/op
Iteration   5: 108.453 us/op


Result: 101.880 ±(99.9%) 14.897 us/op [Average]
  Statistics: (min, avg, max) = (98.889, 101.880, 108.453), stdev = 3.869
  Confidence interval (99.9%): [86.983, 116.777]

// 3、measureStringBuilderApend
# Run progress: 66.67% complete, ETA 00:00:10
# Fork: 1 of 1
# Warmup Iteration   1: 150.403 us/op
# Warmup Iteration   2: 138.444 us/op
# Warmup Iteration   3: 106.494 us/op
Iteration   1: 111.841 us/op
Iteration   2: 104.562 us/op
Iteration   3: 126.979 us/op
Iteration   4: 124.022 us/op
Iteration   5: 109.198 us/op


Result: 115.320 ±(99.9%) 37.382 us/op [Average]
  Statistics: (min, avg, max) = (104.562, 115.320, 126.979), stdev = 9.708
  Confidence interval (99.9%): [77.938, 152.702]


# Run complete. Total time: 00:00:32
Benchmark                                        Mode  Samples      Score  Score error  Units
c.e.d.q.MyBenchmark.measureStringApend           avgt        5  68626.325    19086.757  us/op
c.e.d.q.MyBenchmark.measureStringBufferApend     avgt        5    101.880       14.897  us/op
c.e.d.q.MyBenchmark.measureStringBuilderApend    avgt        5    115.320       37.382  us/op

3.2.2 HashKey冲突
 @Benchmark
    public void measureHashMap() throws IOException {
        Map<HashedKey, String> map = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            map.put(new HashedKey(i), "value");
        }
    }
    private static class HashedKey {
        final int key;

        HashedKey(int key) {
            this.key = key;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (obj instanceof HashedKey) {
                return key == ((HashedKey)obj).key;
            }

            return false;
        }

        @Override
        public int hashCode() {
            return key;
        }
    }

    @Benchmark
    public void measureCollidedHashMap() throws IOException {
        Map<CollidedKey, String> map = new HashMap<>();
        for (int i = 0; i < 10000; i++) {
            map.put(new CollidedKey(i), "value");
        }
    }
    private static class CollidedKey {
        final int key;

        CollidedKey(int key) {
            this.key = key;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) {
                return true;
            }

            if (obj instanceof CollidedKey) {
                return key == ((CollidedKey)obj).key;
            }

            return false;
        }

        @Override
        public int hashCode() {
            return key % 10;
        }
    }

3.3 注解说明

@BenchmarkMode(Mode)

表示 JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。目前JMH 共有四种模式:

  • Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
  • AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
  • SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
  • SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。

@State(Scope.Thread)
State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。

  • Thread: 该状态为每个线程独享。
  • Benchmark: 该状态在所有线程间共享。
  • Group:线程组共享一个示例,在测试方法上使用 @Group 设置线程组。

@fork
进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个线程来进行测试。

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

@Measurement
进行 5 次微基准测试,也可用在测试方法上

@Benchmark
表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似。

@OutputTimeUnit
benchmark 结果所使用的时间单位。

@Param
@Param 可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。

@Setup
@Setup 会在执行 benchmark 之前被执行,正如其名,主要用于初始化。

@TearDown
@TearDown 和 @Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。

Iteration
Iteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。

include
benchmark 所在的类的名字,注意这里是使用正则表达式对所有类进行匹配的。

参考:
JMH - Java 代码性能测试的终极利器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值