【Java】使用Microbenchmarking (JMH)进行基准测试

448 阅读1分钟

【资源】

1. JMH介绍

JMH主要是为了测量那些体量比较“小”的代码,通常运行的时间为次毫秒级别,代码一般要跟I/O开销无关。如:

2. 举个例子

参考:github.com/eugenp/tuto…

我们都知道String连接有三种方式,那么我们就写一个测试,来看看这三种方式的效率:

    1. String直接用+连接。
    1. StringBuffer的append方法,线程安全。
    1. StringBuilder的append方法,线程不安全。

我们用JMH写下benchmark用例:

  • BenchmarkMode:JMH测试使用的模式,这里用的是Throughput:即整体吞吐量,例如“1秒内可以执行多少次调用”,单位是操作数/时间。
  • Warmup:先预热3轮,相当于先空跑3轮,再开始benchmark
  • Measurement:进行10轮测试,每轮5秒。

关于其它注解的解释,请看文章未尾的【参考】第二篇文章的链接,总结的非常好!!!

@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS)
@Threads(8)
@Fork(2)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class StringPerformance {

    @Benchmark
    public String StringAdd() {
        String result = "";
        for (int i = 0; i < 10; i++) {
            result += i;
        }
        return result;
    }

    @Benchmark
    public StringBuilder StringBuilderAdd() {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < 10; i++) {
            stringBuilder.append(i);
        }
        return stringBuilder;
    }

    @Benchmark
    public StringBuffer StringBufferAdd() {
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            stringBuffer.append(i);
        }
        return stringBuffer;
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(StringPerformance.class.getSimpleName())
                .resultFormat(ResultFormatType.JSON)
                .result("StringPerformance.json")
                .build();
        new Runner(options).run();
    }

运行main方法后,生成的报告分四部分,前三部分是我上述三个@Benchmark的方法,最后一部分是汇总。

首先我贴出StringAdd的报告(另外两个方法的报告略)

可以看出每执行一个@Benchmark方法,都会先打印出这次报告的预置参数,如预热了3轮,每轮预热10s钟,测试需要10轮,每轮5s。fork为2,则 JMH 会 fork 出两个进程来进行测试。

# JMH version: 1.33
# VM version: JDK 17.0.4, Java HotSpot(TM) 64-Bit Server VM, 17.0.4+11-LTS-179
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-17.0.4.jdk/Contents/Home/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=64113:/Applications/IntelliJ IDEA CE.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (default, use -Djmh.blackhole.autoDetect=true to auto-detect)
# Warmup: 3 iterations, 10 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 8 threads, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.StringPerformance.StringAdd

# Run progress: 0.00% complete, ETA 00:08:00
# Fork: 1 of 2
# Warmup Iteration   1: 17858.914 ops/ms
# Warmup Iteration   2: 19230.153 ops/ms
# Warmup Iteration   3: 18751.680 ops/ms
Iteration   1: 18379.046 ops/ms
Iteration   2: 18460.512 ops/ms
Iteration   3: 18455.847 ops/ms
Iteration   4: 18268.708 ops/ms
Iteration   5: 18325.388 ops/ms
Iteration   6: 18213.200 ops/ms
Iteration   7: 18124.646 ops/ms
Iteration   8: 18689.763 ops/ms
Iteration   9: 18570.386 ops/ms
Iteration  10: 18655.092 ops/ms

# Run progress: 16.67% complete, ETA 00:06:46
# Fork: 2 of 2
# Warmup Iteration   1: 19125.319 ops/ms
# Warmup Iteration   2: 18887.678 ops/ms
# Warmup Iteration   3: 19061.692 ops/ms
Iteration   1: 19108.268 ops/ms
Iteration   2: 19108.676 ops/ms
Iteration   3: 19173.356 ops/ms
Iteration   4: 18735.736 ops/ms
Iteration   5: 19025.315 ops/ms
Iteration   6: 19129.713 ops/ms
Iteration   7: 19143.510 ops/ms
Iteration   8: 19155.557 ops/ms
Iteration   9: 19184.028 ops/ms
Iteration  10: 18640.211 ops/ms


Result "com.StringPerformance.StringAdd":
  18727.348 ±(99.9%) 321.835 ops/ms [Average]
  (min, avg, max) = (18124.646, 18727.348, 19184.028), stdev = 370.626
  CI (99.9%): [18405.513, 19049.183] (assumes normal distribution)

最后贴出总的报告,可以看出报告还是很符合预期的,即:

  • String直接相加是最慢的(每秒的执行次数最少)。
  • StringBuffer是线程安全的,所以在append的时候需要加锁。
  • StringBuilder是线程不安全的,在append的时候不会加锁,因此吞吐量最高,即Score遥遥领先!
Benchmark                            Mode  Cnt      Score     Error   Units
StringPerformance.StringAdd         thrpt   20  18727.348 ± 321.835  ops/ms
StringPerformance.StringBufferAdd   thrpt   20  23633.983 ± 294.191  ops/ms
StringPerformance.StringBuilderAdd  thrpt   20  54591.306 ± 504.498  ops/ms

然后在项目根目录下,会生成StringPerformance.json文件。原因是我在main函数中加入了resultFormat方法为ResultFormatType.JSON,以及result方法,填的是文件路径+文件名,默认为根目录。

3. 可视化报告

有两个网站都可提供可视化图形报告,需要我们上传.json的文件。

第一个网站的界面

image.png

鼠标移到每个方法(蓝色条纹)上面,可以看到每轮的吞吐量: image.png

第二个网站的界面

image.png

这个网站的github:deepoove.com/jmh-visual-…

4. 其它,IntelliJ idea插件:

github.com/artyushov/i…

image.png


参考: