我有一个计算事件的类。它看起来像这样:
public class Counter {
private static final long BUCKET_SIZE_NS = Duration.ofMillis(100).toNanos();
...
private long nextBucketNum() {
return clock.getTime() / BUCKET_SIZE_NS;
}
public void count() {
...
final long num = nextBucketNum();
...
}
...
}
根据JMH报告,如果我从字段中删除static
修饰符(打算使其成为类参数),计数吞吐量 * 下降 * 超过25%。
为static
用例生成的字节码:
INVOKEINTERFACE Clock.getTime ()J (itf)
GETSTATIC Counter.BUCKET_SIZE_NS : J
LDIV
对于non-static
,则为:
INVOKEINTERFACE Clock.getTime ()J (itf)
ALOAD 0
GETFIELD Counter.BUCKET_SIZE_NS : J
LDIV
我是否在执行性能测试时遇到了某种类型的死代码消除问题,或者是某种级别的合理微优化(如JIT或超线程)?
在单线程和多线程性能指标评测中都存在差异。
使用环境:
JMH version: 1.34
VM version: JDK 1.8.0_161, Java HotSpot(TM) 64-Bit Server VM, 25.161-b12
macOS Monterey 12.2.1
Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz
2条答案
按热度按时间kiz8lqtg1#
这里有两个优化:
为了进一步了解这一点,我建议您使用
perfasm
运行基准测试,看看循环进行到哪里,生成了什么汇编代码。狩猎愉快!
bakd9h0s2#
JVM将静态final字段优化为真正的常量,但它不会对示例字段执行相同的操作。理论上,代码可以被分析和证明,以显示字段总是相同的,但这更复杂。此外,final字段不会被视为真正的final,因为反射后门。有一个Jira项目跟踪这个问题,但我现在找不到它。在内部,JDK使用特殊的
@Stable
注解来优化对最终示例字段的访问。但是,即使您可以使用这个注解,仍然需要额外的分析来证明该字段对于所有示例都是相同的。在大多数情况下,分配字段的代码需要完全内联才能进行分析。如果
Duration.ofMillis
调用被实现为返回一个随机数呢?当然不是,但是没有分析,编译器怎么能确定呢?