基准测试工具JMH

junit用来测方法对不对 ab用来压测http服务
jhm是用测java方法性能的, 比如每秒能运行多少次,每次执行的耗时

jhm是写jdk的那些人整的
所以还是很可靠的

参考 官方介绍
https://openjdk.java.net/projects/code-tools/jmh/
官方示例代码– 点击左侧zip打包下载
https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

工程创建

1
2
3
4
5
6
7
8
mvn archetype:generate \
  -DinteractiveMode=false \
  -DarchetypeGroupId=org.openjdk.jmh \
  -DarchetypeArtifactId=jmh-java-benchmark-archetype \
  -DarchetypeVersion=1.22 \
  -DgroupId=fluffy.mo \
  -DartifactId=test2 \
-Dversion=1.0 

可以把java换成以下语言

1
2
3
4
jmh-java-benchmark-archetype
jmh-scala-benchmark-archetype
jmh-kotlin-benchmark-archetype
jmh-groovy-benchmark-archetype

修改pom.xml 增加以下属性
以整 jdk版本为11

1
2
3
4
5
6
<properties>
    <javac.target>11</javac.target>
    <java.version>11</java.version>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
</properties>

测试字符串拼接效率

拼接字符串时, 五种方式的效率测试
StringBuilder、 StringBuffer、String-Concat、String-Format、直接相加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package fluffy.mo;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

import java.util.List;
import java.util.concurrent.TimeUnit;

public class MyBenchmark {
    @Benchmark
    public void testStringBuffer(MyState st, Blackhole blackhole) {
        var ss = new StringBuffer(prefix()).append(st.s1).append(st.s2).append(st.s3).append(st.s4).append(st.s5).toString();
        blackhole.consume(ss);
    }

    @Benchmark
    public void testStringBuilder(MyState st, Blackhole blackhole) {
        var ss = new StringBuilder(prefix()).append(st.s1).append(st.s2).append(st.s3).append(st.s4).append(st.s5).toString();
        blackhole.consume(ss);
    }

    @Benchmark
    public void testStringConcat(MyState st, Blackhole blackhole) {
        var ss = prefix().concat(st.s1).concat(st.s2).concat(st.s3).concat(st.s4).concat(st.s5);
        blackhole.consume(ss);
    }

    @Benchmark
    public void testStringAdd(MyState st, Blackhole blackhole) {
        blackhole.consume(prefix() + st.s1 + st.s2 + st.s3 + st.s4 + st.s5);
    }

    @Benchmark
    public void testStringFormat(MyState st, Blackhole blackhole) {
        blackhole.consume(String.format("str-%s%s%s%s%s", st.s1, st.s2, st.s3, st.s4, st.s5));
    }

    private String prefix() {
        return "str-";
    }

    @State(Scope.Thread)
    public static class MyState {
        public String s1, s2, s3, s4, s5;

        @Setup
        public void myInit() {
            String ss = List.of(11, 22, 333, 4444, 55555).toString();
            var arr = ss.split(",");
            s1 = arr[0];
            s2 = arr[1];
            s3 = arr[2];
            s4 = arr[3];
            s5 = arr[4];
            System.out.println("初始化状态");
        }
    }

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
                // 导入要测试的类 -- 正则匹配
                .include(MyBenchmark.class.getSimpleName())
                .mode(Mode.Throughput)
                // 预热2轮 持续100毫秒--不计分数
                .warmupIterations(3).warmupTime(TimeValue.microseconds(100))
                // 测量8轮--统计分数
                .measurementIterations(8).measurementTime(TimeValue.seconds(1))
                .forks(1)
                // 打印结果时使用的单位
                .timeUnit(TimeUnit.MILLISECONDS)
                .build();
        new Runner(opt).run();
    }

}

运行结果

1
2
3
4
5
6
Benchmark                       Mode  Cnt      Score      Error   Units
MyBenchmark.testStringAdd      thrpt    8  15017.766 ± 2728.661  ops/ms
MyBenchmark.testStringBuffer   thrpt    8  18837.796 ± 2774.093  ops/ms
MyBenchmark.testStringBuilder  thrpt    8  18787.273 ± 2744.898  ops/ms
MyBenchmark.testStringConcat   thrpt    8   8201.163 ± 1032.904  ops/ms
MyBenchmark.testStringFormat   thrpt    8    540.847 ±  264.437  ops/ms

看起来 format 效果很差

避免jvm优化导致测试有误

举例

1
2
3
4
5
6
7
@Benchmark
public int case01() {
    int x = 1;
    int y = 2;
    int sum = x + y;
    return sum;
}

对于case01这段代码,可能会被jvm优化掉。
比如变成 return 3; 甚至调用case01()方法被替换成 3 。

换成这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@State(Scope.Thread)
public static class MyState {
    public int a = 1;
    public int b = 2;
}

@Benchmark
public int case02(MyState state) {
    int sum = state.a + state.b;
    return sum;
}

参数对jvm不可预知,也就避免优化掉了,测试结果才会靠谱