LinuxPerfAsmProfiler可以显示Java 8的Java代码对应的汇编热点,但无法显示Java 14的汇编热点。

6
在调查与Spring的org.springframework.util.ConcurrentReferenceHashMap(截至spring-core-5.1.3.RELEASE)实例化相关的问题时,我使用了随JMH一起提供的LinuxPerfAsmProfiler来分析生成的汇编代码。
我只是简单地运行了这个。
@Benchmark
public Object measureInit() {
  return new ConcurrentReferenceHashMap<>();
}

在JDK 8上进行基准测试可以识别出一个非明显的热点问题。
  0.61%        0x00007f32d92772ea: lock addl $0x0,(%rsp)     ;*putfield count
                                                             ; - org.springframework.util.ConcurrentReferenceHashMap$Segment::&lt;init&gt;@11 (line 476)
                                                             ; - org.springframework.util.ConcurrentReferenceHashMap::&lt;init&gt;@141 (line 184)
 15.81%        0x00007f32d92772ef: mov    0x60(%r15),%rdx

这对应于对一个易失字段进行不必要的默认值赋值。
protected final class Segment extends ReentrantLock {
  private volatile int count = 0;
}

而且Segment又在CCRHM的构造函数中循环实例化:

public ConcurrentReferenceHashMap(
    int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
  this.loadFactor = loadFactor;
  this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
  int size = 1 << this.shift;
  this.referenceType = referenceType;
  int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
  this.segments = (Segment[]) Array.newInstance(Segment.class, size);
  for (int i = 0; i < this.segments.length; i++) {
   this.segments[i] = new Segment(roundedUpSegmentCapacity);
  }
}

所以指令很可能非常热门。完整的组装布局可以在我的gist中找到。
然后我在JDK 14上运行相同的基准测试,并再次使用LinuxPerfAsmProfiler,但现在我没有任何明确指向volatile int count = 0捕获的汇编代码
寻找lock addl $0x0指令,这是在lock前缀下赋值为0,我找到了这个。
  0.08%                          │  0x00007f3717d46187:   lock addl $0x0,-0x40(%rsp)
 23.74%                          │  0x00007f3717d4618d:   mov    0x120(%r15),%rbx

这段代码很可能对应于volatile int count = 0,因为它紧随Segment的超类ReentrantLock的构造函数调用之后。
  0.77%                          │  0x00007f3717d46140:   movq   $0x0,0x18(%rax)              ;*new {reexecute=0 rethrow=0 return_oop=0}; - java.util.concurrent.locks.ReentrantLock::&lt;init&gt;@5 (line 294); - org.springframework.util.ConcurrentReferenceHashMap$Segment::&lt;init&gt;@6 (line 484); - org.springframework.util.ConcurrentReferenceHashMap::&lt;init&gt;@141 (line 184)
  0.06%                          │  0x00007f3717d46148:   mov    %r8,%rcx
  0.05%                          │  0x00007f3717d4614b:   mov    %rax,%rbx
  0.03%                          │  0x00007f3717d4614e:   shr    $0x3,%rbx
  0.74%                          │  0x00007f3717d46152:   mov    %ebx,0xc(%r8)
  0.06%                          │  0x00007f3717d46156:   mov    %rax,%rbx
  0.05%                          │  0x00007f3717d46159:   xor    %rcx,%rbx
  0.02%                          │  0x00007f3717d4615c:   shr    $0x14,%rbx
  0.72%                          │  0x00007f3717d46160:   test   %rbx,%rbx
                             ╭   │  0x00007f3717d46163:   je     0x00007f3717d4617f
                             │   │  0x00007f3717d46165:   shr    $0x9,%rcx
                             │   │  0x00007f3717d46169:   movabs $0x7f370a872000,%rdi
                             │   │  0x00007f3717d46173:   add    %rcx,%rdi
                             │   │  0x00007f3717d46176:   cmpb   $0x8,(%rdi)
  0.00%                      │   │  0x00007f3717d46179:   jne    0x00007f3717d46509
  0.04%                      ↘   │  0x00007f3717d4617f:   movl   $0x0,0x14(%r8)
  0.08%                          │  0x00007f3717d46187:   lock addl $0x0,-0x40(%rsp)
 23.74%                          │  0x00007f3717d4618d:   mov    0x120(%r15),%rbx

问题是我在生成的汇编代码中根本没有看到任何关于putfield count的提及。
有人能解释一下为什么我看不到它吗?

3
似乎某些JIT优化破坏了编译代码与bci之间的映射关系。如果使用-XX:MaxInlineLevel=0运行JDK 14,则“putfield count”注释将再次可见。 - apangin
@apangin 谢谢!那我应该向 hotspot-compiler-devjmh-dev 邮件列表报告吗?你觉得呢? - Sergey Tsypanov
这绝对不是JMH的问题。-XX:+PrintAssembly缺少调试信息。因此,hotspot-compiler-dev将更加合适。 - apangin
顺便问一下,这个标志-XX:MaxInlineLevel=0会不会扭曲配置文件,导致禁用内联时也可能禁用其他优化呢?如果是这样的话,你知道有哪些类似的情况吗? - Sergey Tsypanov
它并不直接禁用其他优化,但肯定会使它们变得不那么有效,因为可以应用优化的范围明显减少了。请参见https://stackoverflow.com/a/52468276/3448419 - apangin
显示剩余2条评论
1个回答

1

事实证明,你不能使用为JDK 8构建的hsdis与JDK 11一起使用。为了完美匹配,您需要从JDK源代码构建hsdis,然后构建JDK本身,并在此特定构建上运行应用程序。

当我调查String构造函数中缺少边界检查消除?时,这种方法对我非常有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接