一、注意事项
1.1、避免DCE(Dead Code Elimination)
DCE 就是 JVM 在优化时为我们主从擦去上下文无关的内容,甚至是计算后不会使用的代码。
比如:
int x = 1;
int y = 2;
int z = x * y;
如果基准测试方法内容为上述内容,则 JVM 可能认定其为空的方法,影响基准测试。
1.2、Blackhole 使用
Blackhole 能够在无法返回的时候保证
案例代码如下:JmhDemo.java
1.3、避免常量折叠(Constant Folding)
Java 早期的编译优化会造成常量折叠。
如:
private int x = 1;
private int y = x * 100;
如上述代码,在编译期间 y 会被直接赋值为 100 ,这就是所谓的常量折叠。
1.4、避免循环展开(Loop Unwinding)
在基准方法中要尽可能少的,甚至不适用循环,因为循环代码在运行阶段(JVM后期优化)会被重点关照,进行优化,这种优化叫做”循环展开“。
如:
int sum = 0;
for( int i = 0 ; i < 100 ; i++ ){
sum += i;
}
<b>可能</b>被优化成
int sum = 0;
for( int i = 0 ; i < 20 ; i += 5 ){
sum += i;
sum += i + 1;
sum += i + 2;
sum += i + 3;
sum += i + 4;
}
1.5、Fork 使用
Fork 可以用于避免 Profile-guided optimizations。
Fork 通常设为 1 ,表示每次运行基准测试方法都会开辟新的进程运行,防止环境污染。
Profile-guided optimizations 案例
代码如下:JmhDemo.javaInc1
和 Inc2
实现完全一样,同时基准测试方法也一样,但是两者的基准测试可能存在差异
二、高级用法
2.1、Asymmetric Benchmark
针对某个方法同时进行读写操作,测试线程安全。
案例:针对 AtomicInteger 同时进行读写操作,验证性能。
代码如下:JmhDemo.java
执行结果如下
2.2、Interrupts Benchmark
在某些情况下,执行容器的某些操作会引起阻塞,该阻塞不是容器无法保证线程安全问题引起的,而是JMH 框架的机制引起的,比如:BlockingQueue。
BlockingQueue 在获取数据,如果是队列中没有数据会阻塞。或者在往队列汇总放数据,队列满了导致的阻塞问题。
案例代码:JmhDemo.java
在 put or take 操作,在 warmup 或者 measure 时都有可能出现阻塞的问题,一旦出现阻塞,就会导致整个基准测试陷入阻塞,阻塞状态只有等到批次iteration 超时(默认:10分钟)后才会继续进行。
解决:JMH 提供每个批次超时设置,超时的批次不会被纳入度量统计中
解决代码:JmhDemo.java
三、案例:各大Map容器 基准测试
测试MAP 容器
- ConcurrentHashMap
- ConcurrentSkipListMap
- HashTable
- Collections.synchronizedMap
测试代码JmhDemo.java
测试结果
type 1 的性能最好,type 1 对应 ConcurrentHashMap