赞
踩
JMH(Java Microbenchmark Harness)是一个用于编写、构建和运行Java微基准测试的框架。它提供了丰富的注解和工具,用于精确控制测试的执行和结果测量,从而帮助我们深入了解代码的性能特性。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
</dependency>
要使用JMH进行微基准测试,你需要在项目的构建系统(如Maven或Gradle)中引入JMH的依赖。
下面是一个简单的JMH测试示例,用于比较直接访问数组元素和通过方法访问数组元素的性能差异。
package cn.pottercoding.jmh; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.concurrent.TimeUnit; /** * @author Mr.Sun * @since 2024年03月20日 * * 一个简单的JMH测试示例,用于比较直接访问数组元素和通过方法访问数组元素的性能差异。 */ @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS) @State(Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) public class ArrayAccessBenchmark { // 数组大小 private static final int ARRAY_SIZE = 10000; // 初始化数组 private int[] array = new int[ARRAY_SIZE]; // 初始化方法,用于填充数组 @Setup public void setup() { for (int i = 0; i < ARRAY_SIZE; i++) { array[i] = i; } } // 直接访问数组元素 @Benchmark public int directAccess() { int sum = 0; for (int i = 0; i < ARRAY_SIZE; i++) { sum += array[i]; } return sum; } // 通过方法访问数组元素 @Benchmark public int methodAccess() { int sum = 0; for (int i = 0; i < ARRAY_SIZE; i++) { sum += getElement(i); } return sum; } // 获取数组元素的方法 private int getElement(int index) { return array[index]; } // 主方法,用于运行基准测试 public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(ArrayAccessBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); } }
接下来让我们一起看下上面示例代码中用到的每个JMH注解的含义:
@BenchmarkMode
这个注解用于指定基准测试的模式。Mode.AverageTime
表示测量的是每次操作的平均执行时间。JMH提供了多种模式,如Throughput
(吞吐量,即每秒完成的操作数)和SampleTime
(采样时间)等。
@Warmup
和 @Measurement
@Warmup
:用于指定预热阶段的相关设置。预热阶段用于让JVM的JIT编译器有时间优化测试代码,并使得缓存、垃圾回收等达到稳定状态。iterations指定预热迭代的次数,time和timeUnit分别指定预热阶段的总时间和时间单位。
@Measurement
:用于指定实际测量阶段的相关设置。这些设置类似于预热阶段,但它们是用于收集基准测试结果的。
@State
这个注解用于定义测试状态。Scope.Thread
表示每个测试线程都有自己的测试状态实例。这样可以避免多线程之间的状态共享问题。其他可能的范围还包括Scope.Benchmark
(所有线程共享同一个状态实例)和Scope.Group
(每个测试组共享一个状态实例)。
@OutputTimeUnit
这个注解用于指定输出结果的时间单位。在这个例子中,TimeUnit.NANOSECONDS
表示输出的时间将以纳秒为单位。
@Setup
这个注解用于标记在每次基准测试方法运行之前应该执行的方法。它通常用于初始化测试所需的数据或状态。在这个例子中,setup()方法用于填充数组。
@Benchmark
这个注解用于标记一个基准测试方法。JMH会运行这个方法多次,并收集相关的性能数据。在这个例子中,directAccess()
和methodAccess()
都是基准测试方法,它们分别测试直接访问数组元素和通过方法访问数组元素的性能。
@Param
虽然这个注解在上面的示例代码中并没有使用,但它是一个常见的JMH注解,用于参数化基准测试。通过在测试类中的字段上使用@Param注解,并指定不同的值,你可以为同一个基准测试方法创建多个不同的测试场景。
执行结果如下:
代码执行结果分析:
directAccess()
方法
这个方法直接通过数组索引访问数组元素,并计算它们的和。由于它直接操作数组的内存位置,因此通常是最快的访问方式。在大多数情况下,directAccess()
方法应该会获得较低的平均执行时间。
methodAccess()
方法
这个方法通过调用 getElement()
方法来访问数组元素。虽然getElement()
方法内部也是直接访问数组,但是方法调用的开销(如参数传递、栈帧创建等)通常会比直接访问要高一些。因此,methodAccess()
方法的平均执行时间很可能会比 directAccess()
方法稍长。
结果比较
JMH会运行每个基准测试方法多次,并收集每次运行的执行时间。然后,它会计算这些时间的平均值、标准差等统计信息,并将它们输出到控制台或文件中。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。