美文网首页
JIT Inline的一些问题

JIT Inline的一些问题

作者: zizon | 来源:发表于2019-11-16 13:05 被阅读0次

    大致这么一段逻辑

    void test(item){
        if(array_list == null){
            array_list = new ArrayList<>();
        }
    
        if(!array_list.contains(item)) {
            array_list.add(item);
        }
    }
    

    这段代码在两台机器和不同用户之间会有些比较明显的性能差异.

    具体是有两台机器A,B.
    A为一台KVM,B是物理机器.
    A宿主机器跟B是同型号CPU.

    现象是一个benchmark在A上大概是5分钟不到.
    而B是20-30分钟左右.

    另外一个就是在B上以root和非root用户偶尔也会有些可见差别.

    这个case的实际逻辑是是加载并解析一个配置,并且是单线程的.
    所以整体过程应该是deterministic的.
    理论上不应该有这么明显的差异的.

    perf了一下.
    B上面有一段是change_protection_range会相对显著地跟A有所区别.

    大概翻了下对应内核版本的实现,看上去有一些huge page/large page相关的东西.
    但是看系统参数和JVM配置,largepage相关的选项也并没有打开.

    而且对比了下两者的环境变量和生效配置以及相应的.so也是一致的.

    所以从系统层面是上来说,应该都是没差别的.

    于是看了下VMThread的safepoint信息.

    从日志里看是有一些bias lock的revoke情况.
    但是从代码逻辑上来说并没有显著的synchronized应用.
    这点就有些奇怪了.

    把bias lock disable之后greys attach上去做profile.
    然后发觉貌似性能是上去了.
    两边都差不多是每秒20-30w左右的调用次数.

    但是感觉不是很有说服力.

    一个想法是bias lock带来的一点object的开销在这种频繁可能会有新对象创建的场景下被放大.
    这样的话可能会跟perf的change_protection_range有联动.
    因为可能会对cacheline有影响,毕竟一个是KVM一个是物理机器.

    为了确定关联性,把profile去掉之后发觉性能差异有出现了.

    那么为什么profile会影响到性能呢?
    而且是positive的影响.

    想起agent在retransform的时候,redefine class会触发deoptimize.

    那么,如果是JIT的问题的话,agent deoptimize能提升性能,也就意味着触发了某个优化规则导致的.

    尝试不同的compilation policy.
    发现在non-tiered compilation(simple/stack walk)的情况下是ok的.
    但是开启tiered compilation(simple threshold/advance threshold)之后则有大幅下降.

    用perf-map-agent重新perf看了下.
    发现non-tiered的情况下,hot code是array list的contains/indexOf.
    而tiered的则是test和equals.

    到这里其实就已经很明显了.

    non-tiered只inline array list的相关调用,包括indexOf里的equals.
    所以perf只看到了contains或者indexOf.

    而tiered JIT了test和equals.

    实际上看生产的assembly,除了做inline之外,同时还对indexOf做了loop unroll.

    但是看perf的结果是test和equals同时被JIT了.
    那么也就意味着test没有把equals inline进去.

    看JIT生产的assembly证实了,对于整个调用链,也就是indexOf里的equals还是callq调用的.

    所以应该是某个机制限制了把equals inline进去.
    从而loop unroll之后反而引入了函数调用开销.

    把inline日志打开可以看到test的inline有两类信息.
    一个是callee is too large.
    另外一个是already compiled into a big method.

    这就可以解释了.

    因为test本身不复杂,所以没有触发自身被caller inline进去.
    于是hot code也只会inline这个调用链以下的.
    而由于inline和loop unroll使得本身的code size变得比较大,在尝试进一步inline equals的时候被拒绝了.

    调整这两个相关参数之后再看确实没有了equals的调用,同时性能保持了一致性.

    而再回头看为什么KVM和物理机会有性能差异的时候,大概就是因为compiler thread的数量和并发程度不同.
    因此在尝试inline equals的时候会出现时机和结果的不一致性.

    所以想想的话,从代码风格上来说,确实compact和reusable的会相对来说比较友好一点.

    像Stream这种batch process的风格来说的话,确实有可能for之类的手工展开的好.
    因为一个loop unroll,一个是函数主题一般都比较小.
    再就是手工展开的话,很难说不会触发像上面这种inline不完整反而受累的情况.

    相关文章

      网友评论

          本文标题:JIT Inline的一些问题

          本文链接:https://www.haomeiwen.com/subject/xdlyictx.html