JMH基准测试

2022-09-21 09:56:55 浏览数 (1)

前言

我们写的代码性能如何?用StopWatch来监控方法得出方法执行时间就是准确的?

使用JMH就可以回答第一个问题。JMH是方法级别的性能测试工具,并且是openjdk官方开发的(值得信赖),它有很多针对性能测试的功能,例如预热,该功能就可以解决StopWatch测试不准确的问题。

StopWatch测试不准确的原因如下:

  1. Java8引入的lambda表达式具有首次初始化开销。

lambda表达式的类是运行时生成的,而不是从类路径加载的。然而,生成类并不是速度变慢的原因。毕竟,生成一个结构简单的类比从外部源加载相同的字节还要快。内部类也必须加载。但是,当应用程序以前没有使用lambda表达式时,必须加载用于生成lambda类的框架(Oracle当前的实现在幕后使用ASM)。这是导致十几个内部使用的类(而不是lambda表达式本身)减速、加载和初始化的真正原因。

  1. JIT编译器的优化 HotSpot虚拟机采用的是解释执行和编译执行的混合执行方式,两者的性能是有区别的。JIT编译器可将字节码编译成本机机器代码,其运行速度会更快。但是编译是有成本的,虚拟机只会编译热点代码,这是可以通过jvm参数控制的。

准备工作

导入依赖

代码语言:javascript复制
<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>
</dependency>

使用

测试String,StringBuilder,StringBuffer字符串拼接

代码语言:javascript复制
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public class JmhForString {

    public static final int STRING_ELEMENT_COUNT = 10_000;

    public static void main(String[] args) throws RunnerException {
        final Options options = new OptionsBuilder().include(JmhForString.class.getName())
                                                    .build();
        new Runner(options).run();
    }

    @Benchmark
    public void testString(Blackhole blackhole) {
        String str = "";
        for (int i = 0; i < STRING_ELEMENT_COUNT; i  ) {
            str  = i;
        }
        blackhole.consume(str);
    }

    @Benchmark
    public void testStringBuilder(Blackhole blackhole) {
        StringBuilder str = new StringBuilder("");
        for (int i = 0; i < STRING_ELEMENT_COUNT; i  ) {
            str.append(i);
        }
        blackhole.consume(str);
    }

    @Benchmark
    public void testStringBuffer(Blackhole blackhole) {
        StringBuffer str = new StringBuffer("");
        for (int i = 0; i < STRING_ELEMENT_COUNT; i  ) {
            str.append(i);
        }
        blackhole.consume(str);
    }
}
输出结果
代码语言:javascript复制
Benchmark                       Mode  Cnt    Score   Error  Units
JmhForString.testString         avgt    2  251.133          ms/op
JmhForString.testStringBuffer   avgt    2    0.373          ms/op
JmhForString.testStringBuilder  avgt    2    0.193          ms/op

测试HashMap和CurrentHashMap的Get、Put

代码语言:javascript复制
@Fork(1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 3, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class JmhForHashMap {

    public static final int TEST_COUNT = 100_000;
    private HashMap<Object, Object> hashMapForGet;
    private ConcurrentHashMap<Object, Object> concurrentHashMapForGet;

    public static void main(String[] args) throws RunnerException {
        final Options options = new OptionsBuilder().include(JmhForHashMap.class.getName())
                                                    .build();
        new Runner(options).run();
    }

    @Setup
    public void init() {
        this.hashMapForGet = testHashMapPut();
        this.concurrentHashMapForGet = testConcurrentMapPut();
    }

    @Benchmark
    public HashMap<Object, Object> testHashMapPut() {
        HashMap<Object, Object> hashMap = Maps.newHashMap();
        IntStream.rangeClosed(1, TEST_COUNT)
                 .boxed()
                 .forEach(i -> hashMap.put(i / 2, i));
        return hashMap;
    }

    @Benchmark
    public ConcurrentHashMap<Object, Object> testConcurrentMapPut() {
        ConcurrentHashMap<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
        IntStream.rangeClosed(1, TEST_COUNT)
                 .boxed()
                 .forEach(i -> concurrentHashMap.put(i / 2, i));
        return concurrentHashMap;
    }

    @Benchmark
    public void testHashMapGet() {
        IntStream.range(0, TEST_COUNT)
                 .boxed()
                 .forEach(i -> hashMapForGet.get(i));
    }

    @Benchmark
    public void testConcurrentHashMapGet() {
        IntStream.range(0, TEST_COUNT)
                 .boxed()
                 .forEach(i -> concurrentHashMapForGet.get(i));
    }

}
输出结果
代码语言:javascript复制
Benchmark                                Mode  Cnt    Score   Error  Units
JmhForHashMap.testConcurrentHashMapGet  thrpt       764.034          ops/s
JmhForHashMap.testConcurrentMapPut      thrpt       112.291          ops/s
JmhForHashMap.testHashMapGet            thrpt       946.438          ops/s
JmhForHashMap.testHashMapPut            thrpt       277.580          ops/s

0 人点赞