Java 微基准测试 JMH

Java JMH About 4,481 words

添加依赖

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

IDEA 插件

https://github.com/artyushov/idea-jmh-plugin

@Warmup

预热。可以用在类或者方法上。

预热过程的测试数据,是不记录测量结果的。

@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)

注解含义:对代码预热总计3秒(迭代3次,每次1秒)

  • iterations:预热阶段的迭代数。
  • time:每次预热的时间。
  • timeUnit:时间单位,默认的单位是秒。

@Measurement

真正的迭代次数。

MeasurementWarmup的参数是一样的。

@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)

@BenchmarkMode

基准测试类型。可以用在类或者方法上。

注解中值是一个数组,可以配置多个统计维度,还可以设置为Mode.All,即全部执行一遍。

  • Throughput:整体吞吐量,每秒执行了多少次调用,单位时间内的调用量等,单位为ops/time
  • AverageTime:平均耗时,每次执行的平均时间。如果这个值很小不好辨认,可以把统计的单位时间调小一点,单位为time/op
  • SampleTime:随机取样,最后输出取样结果的分布。
  • SingleShotTime:只运行一次,往往同时把Warmup次数设为0,用于测试冷启动时的性能。其实和传统的main方法没有什么区别。
  • All:上面的所有模式都执行一次。
@BenchmarkMode(Mode.Throughput)
// @BenchmarkMode({Mode.Throughput, Mode.AverageTime})

@Fork

使用进程个数(注意不是线程)。可以用在类或者方法上。

一般设置成1,表示只使用一个进程进行测试。

如果这个数字大于1,表示会启用新的进程进行测试;但如果设置成0,程序依然会运行,不过这样是在用户的JVM进程上运行的,但不推荐这么做。

平常的测试中,也可以适当增加Fork数,来减少测试的误差。

@Fork注解有一个参数叫做jvmArgsAppend,我们可以通过它传递一些JVM的参数。

@Fork(value = 3, jvmArgsAppend = {"-Xmx2048m", "-server", "-XX:+AggressiveOpts"})

@Threads

Fork是面向进程的,而Threads是面向线程的。可以用在类或者方法上。

如果配置了Threads.MAX,则使用和处理机器核数相同的线程数。

@Threads(2)

@Group

把测试方法进行归类。只能作用在方法上。

如果单个测试文件中方法比较多,或者需要将其归类,则可以使用这个注解。

@State

指定类中变量的作用范围。它有三个取值。

@State可以被继承使用,如果父类定义了该注解,子类则无需定义。

  • Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能。
  • Scope.Group:联系上面的@Group注解,同一个线程在同一个Group里共享实例。
  • Scope.Thread:默认的State,每个测试线程分配一个实例,互不影响。
@State(Scope.Thread)

@Param

指定某项参数的多种情况,只能作用在字段上。

使用该注解必须定义@State注解。

特别适合用来测试一个函数在不同的参数输入的情况下的性能。

@Param(value = {"10", "50", "100"})
private int length;

JMH 陷阱

在使用JMH的过程中,一定要避免一些陷阱。

比如JIT优化中的死码消除,比如以下代码:

@Benchmark
public void testStringAdd() {
    String a = "";
    for (int i = 0; i < length; i++) {
        a += i;
    }
}

JVM可能会认为变量a从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。

JMH提供了两种方式避免这种问题,一种是将这个变量作为方法返回值return a,一种是通过Blackholeconsume来避免JIT的优化消除。

其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用Fork隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的可以阅读 https://github.com/lexburner/JMH-samples 了解全部的陷阱。

示例

@BenchmarkMode(Mode.Throughput)
//@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(2)
public class RandomBenchmark {

    @Param(value = {"10", "50", "100"})
    private int length;

    @Benchmark
    public void testRandom(Blackhole blackhole) {
        double random = 0;
        for (int i = 0; i < length; i++) {
            random = Math.random();
        }
        blackhole.consume(random);
    }

    @Benchmark
    public void testSecureRandom(Blackhole blackhole) {
        for (int i = 0; i < length; i++) {
            SecureRandom secRandom = new SecureRandom();
            blackhole.consume(secRandom.nextInt());
        }
    }

    public static void main(String[] args) throws Exception {
        Options opts = new OptionsBuilder()
            .include(RandomBenchmark.class.getSimpleName())
            .resultFormat(ResultFormatType.JSON)
            .build();
        new Runner(opts).run();
    }

}

测试结果

Error是正负偏差

Benchmark                         (length)   Mode  Cnt     Score     Error   Units
RandomBenchmark.testRandom              10  thrpt    5  1575.584 ± 286.636  ops/ms
RandomBenchmark.testRandom              50  thrpt    5   240.167 ± 102.101  ops/ms
RandomBenchmark.testRandom             100  thrpt    5   122.695 ±  46.519  ops/ms
RandomBenchmark.testSecureRandom        10  thrpt    5    48.633 ±  13.283  ops/ms
RandomBenchmark.testSecureRandom        50  thrpt    5    10.155 ±   0.621  ops/ms
RandomBenchmark.testSecureRandom       100  thrpt    5     4.986 ±   0.988  ops/ms

JMH 可视化

JMH支持5种格式的结果:

  • TEXT:导出文本文件。
  • JSON导出成JSON文件。
  • CSV:导出CSV格式文件。
  • SCSV:导出SCSV等格式的文件。
  • LATEX:导出到LATEX,一种基于ΤΕΧ的排版系统。

将测试结果以图表的形式可视化:

开源地址

https://github.com/openjdk/jmh

参考

https://www.cnblogs.com/wupeixuan/archive/2020/06/11/13091381.html

Views: 1,001 · Posted: 2023-06-16

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb/LiteNote

扫描下方二维码关注公众号和小程序↓↓↓

扫描下方二维码关注公众号和小程序↓↓↓


Today On History
Browsing Refresh