JMH
jmh 是java 的微测试工具
运行方式
官方文档 建议用官方的 mvn archetypeArtifactIdb, 他内部会把 你的代码
以及 依赖
打成 一个
jar包(默认打的包时 benchmarks.jar)运行。
这种方式如果遇到类似 依赖 spring 的项目需要注意在 shade plugin 里面添加上 spring 的schmea
1 2 3 4 5 6 7 8 9 10 11
| <configuration> <finalName>${uberjar.name}</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration>
|
不过也可以自己写main 函数运行
1 2 3 4 5 6 7 8 9 10 11
| public class MyBenchmark {
}
public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(MyBenchmark.class.getSimpleName()) .build(); new Runner(opt).run(); }
|
配置
1 2 3 4
| // 配置预热, 几轮,每轮实践 @Warmup(iterations = 1, time = 10, timeUnit = TimeUnit.SECONDS) // 配置测试, 几轮,每轮实践 @Measurement(iterations = 1, time = 20, timeUnit = TimeUnit.SECONDS)
|
初始化环境
有的时候需要, 比如先启动spring, 搞到xx bean ,然后对 它做压测。
这里需要定义一个 state beachmark 启动时会帮你初始化好 state, 将它作为参数传入你的 beachmark 函数。
state 里面的field 可以设置默认参数 @Param("6")
, 所有参数支持数组 @Param({"1", "4", "16", "32"})
1 2 3 4 5 6 7 8 9
| @State(Scope.Benchmark) public static class MyState {
@Param("6") public int clientTimeoutSec; @Param({"1", "4", "16", "32"}) public int ioThread; }
|
注意, 党设置了多个参数, beachmark 运行时会根据不同参数组合, 将beachmark 跑多次
比如上面的代码会运行 1 x 4 = 4
次
beachmark + spring 例子
下面是一个配合spring 的例子
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
| @State(Scope.Benchmark) public static class MyState {
@Param("6") public int clientTimeoutSec; @Param({"1", "4", "16", "32"}) public int ioThread; ApplicationContext applicationContext; TDemoService.FutureIface iface;
@Setup(Level.Trial) public void doSetup() { try { applicationContext = new ClassPathXmlApplicationContext( new String[] { "classpath*:application-context.xml" }); iface = applicationContext.getBean(TDemoService.FutureIface.class); } catch (Throwable e) { LOG.error(e.getMessage(), e); throw e; } }
@TearDown(Level.Trial) public void doTearDown() { ((ClassPathXmlApplicationContext) applicationContext).close(); } }
@Benchmark public void testMethod(MyState state) { try { state.iface.echo(state.clientTimeoutSec, state.ioThread); } catch (Exception e) { e.printStackTrace(); } }
|
根据上面的配置, beachmark 的 clientTimeoutSec 和 ioThread 分别以 [6, 1]
, [6, 4]
, [6, 16]
, [6, 32]
4套配置 运行4次
并给出相应的结果。
参数化运行
有时需要最好可以通过配置跑出不同结果, 这种时候建议上面的 state 全部都配置默认值, 然后通过 喂参数的方式达到运行多套配置的结果。
比如
1
| java -jar benchmarks.jar -p ioThread=1,2,3,4
|
运行时 命令行的 参数会 覆盖
代码内部的参数。
Profiler
支持跑 beachmark 时, 查看对 linux perf, 查看代码的堆栈cpu 消耗比。
列出所有支持的 profilers
1
| java -jar beachmarks.jar -lprof
|
参数化的配置通过添加
注意, 额外的 profiler 也会纳入 beachmark的结果列表, 建议 仅仅添加需要的。
通过类似如下方式将不要的 去除
1
| grep -v ':·stack' benchmark_machine_result.csv
|
我只用了 stack, 可以观察具体哪些方法耗cpu 厉害
fork 和 thread
-f
表示同样的beachmark 跑几次。
-t
表示beachmark 的并发数量。
我是 -f 1 -t 40
结果输出
结果能打出 2份, 一份是 human-readable output
另一份是 machine-readable results
其实 前者的 最后 一段就是 后者。
后者支持的格式 Available formats: text, csv, scsv, json, latex
1 2 3
| -o 设置 前者的 文件名字 -rf 设置 format -rff 设置 后者的 文件名字
|
这里的输出有一个缺点,当使用了 -prof,她的 beachmark 也会被记录下来,可以通过
cat xxx.xx |grep -v ':·stack' > xxx.xx2
过滤掉
tips
建议的使用方式, 如果你有多套参数要测试的话, 建议不要一次性测试多个维度。
比如 ioThread 设置为 {1,4,16,32}
, connCount 设置为 {1,10,100,1000}
这样的话 为了测试各种情况, 她的测试次数将是 一个笛卡尔集 4 * 4 = 16 次
建议测试时,一个一个维度测试, 代码里写上默认配置,然后 通过 配置 -p
设置参数组 覆盖。
reference
http://hg.openjdk.java.net/code-tools/jmh/file/a128fd4a5901/jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_35_Profilers.java
http://openjdk.java.net/projects/code-tools/jmh/