JMH 是 OpenJDK 团队开发的一款基准测试工具,一般用于代码的性能调优,精度甚至可以达到纳秒级别,适用于 java 以及其他基于 JVM 的语言。和 Apache JMeter 不同,JMH 测试的对象可以是任一方法,颗粒度更小,而不仅限于rest api
。
JMH 比较典型的应用场景如下:
- 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
- 对比接口不同实现在给定条件下的吞吐量
- 查看多少百分比的请求在多长时间内完成
JMH主要是通过注解的形式编写测试单元,告诉JMH如何测试,JMH自动生成测试代码,所以在使用JMH进行微基准测试时一定要先了对JMH注解有一定了解,下面就介绍下JMH的注解。
@Benchmark @Benchmark
用于告诉JMH
哪些方法需要进行测试,只能注解在方法上,有点类似junit
的@Test
。在测试项目进行package
时,JMH
会针对注解了@Benchmark
的方法生成Benchmark
方法代码。通常情况下,每个Benchmark
方法都运行在独立的进程中,互不干涉。
@BenchmarkMode
用于指定当前Benchmark
方法使用哪种模式测试。JMH
提供了4种不同的模式,用于输出不同的结果指标,如下:
- Throughput:吞吐量,ops/time。单位时间内执行操作的平均次数
- AverageTime:每次操作所需时间,time/op。执行每次操作所需的平均时间
- SampleTime:同 AverageTime,区别在于 SampleTime会随机取样,最后输出取样结果的分布
- SingleShotTime:同 AverageTime。区别在于 SingleShotTime 只执行一次操作。这种模式的结果存在较大随机性。
- All:上边所有的都执行一遍
@BenchmarkMode支持数组,可以指定多种模式也可以配置All,所有模式都执行一遍
@Warmup和@Measurement @Warmup
和@Measurement
分别用于配置预热迭代和测试迭代。其中,iterations
用于指定迭代次数,time
和timeUnit
用于每个迭代的时间,batchSize
表示执行多少次Benchmark
方法为一个invocation
。
通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:
- Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
- Scope.Group:同一个线程在同一个 group 里共享实例
- Scope.Thread:默认的 State,每个测试线程分配一个实例
这两个注解只能定义在注解了 State 里,其中,@Setup类似于 junit 的@Before,而@TearDown类似于 junit 的@After。
@State(Scope.Thread)
public class JMHSample_05_StateFixtures {double x;
@Setup(Level.Iteration)
public void prepare() {System.err.println("init............");
x = Math.PI;
}
@TearDown(Level.Iteration)
public void check() {System.err.println("destroy............");
assert x >Math.PI : "Nothing changed?";
}
@Benchmark
public void measureRight() {x++;
}
}
这两个注解注释的方法的调用时机,主要受Level
的控制,JMH提供了三种Level
,如下:
- Trial:Benchmark 开始前或结束后执行,如下。Level 为 Benchmark 的 Setup 和 TearDown 方法的开销不会计入到最终结果。
- Iteration:Benchmark 里每个 Iteration 开始前或结束后执行,如下。Level 为 Iteration 的 Setup 和 TearDown 方法的开销不会计入到最终结果。
- Invocation:Iteration 里每次方法调用开始前或结束后执行,如下。
Level 为 Invocation 的 Setup 和 TearDown 方法的开销将计入到最终结果
。
为统计结果的时间单位,可用于类或者方法注解
@Threads每个进程中的测试线程,可用于类或者方法上。
@Fork进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。
JMH 陷阱在使用 JMH 的过程中,一定要避免一些陷阱。
比如 JIT 优化中的死码消除,比如以下代码:
@Benchmark
public void testStringAdd(Blackhole blackhole) {String a = "";
for (int i = 0; i< length; i++) {a += i;
}
}
JVM 可能会认为变量 a 从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。JMH 提供了两种方式避免这种问题,一种是将这个变量作为方法返回值 return a,一种是通过 Blackhole 的 consume 来避免 JIT 的优化消除。其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用 Fork 隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等
IDEA插件JMH plugin
插件可以让我们像使用Junit一样使用JMH。
这个插件可以让我们能够以 JUnit 相同的方式使用 JMH,主要功能如下:自动生成带有 @Benchmark 的方法像 JUnit 一样,运行单独的 Benchmark 方法运行类中所有的 Benchmark 方法比如可以通过右键点击 Generate…,选择操作 Generate JMH benchmark 就可以生成一个带有 @Benchmark 的方法。还有将光标移动到方法声明并调用 Run 操作就运行一个单独的 Benchmark 方法。将光标移到类名所在行,右键点击 Run 运行,该类下的所有被 @Benchmark 注解的方法都会被执行。
集成使用 maven依赖和插件配置org.openjdk.jmh jmh-core 1.21 org.openjdk.jmh jmh-generator-annprocess 1.21 test
编写基准测试@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 5)
@Threads(4)
@Fork(1)
@State(value = Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class StringConnectTest {@Param(value = {"10", "50", "100"})
private int length;
@Benchmark
public void testStringAdd(Blackhole blackhole) {String a = "";
for (int i = 0; i< length; i++) {a += i;
}
blackhole.consume(a);
}
@Benchmark
public void testStringBuilderAdd(Blackhole blackhole) {StringBuilder sb = new StringBuilder();
for (int i = 0; i< length; i++) {sb.append(i);
}
blackhole.consume(sb.toString());
}
public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder()
.include(StringConnectTest.class.getSimpleName())
.result("result.json")
.resultFormat(ResultFormatType.JSON).build();
new Runner(opt).run();
}
}
生成 jar 包执行JMH 官方提供了生成 jar 包的方式来执行,我们需要在 maven 里增加一个 plugin,具体配置如下:
org.apache.maven.plugins maven-shade-plugin 2.4.1 package shade jmh-demo org.openjdk.jmh.Main
运行基准测试
mvn clean install
java -jar target/jmh-demo.jar StringConnectTest
可视化- JMH Visual Chart:http://deepoove.com/jmh-visual-chart/
- JMH Visualizer:https://jmh.morethan.io/
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
文章标题:JMH-Java微基准测试工具套件-创新互联
URL分享:http://scgulin.cn/article/ccpoes.html