JMH
jmh 是java 的微测试工具
运行方式
官方文档 建议用官方的 mvn archetypeArtifactIdb, 他内部会把 你的代码 以及 依赖 打成 一个  jar包(默认打的包时 benchmarks.jar)运行。
这种方式如果遇到类似 依赖 spring 的项目需要注意在 shade plugin 里面添加上 spring 的schmea
| 12
 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 函数运行
| 12
 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();
 }
 
 | 
配置
| 12
 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"})
| 12
 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 的例子
| 12
 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
| 12
 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/