Hotspot 方法编译之CompilationPolicy 源码解析

 目录

1、定义

 2、compilationPolicy_init

3、AdvancedThresholdPolicy

4、AdvancedThresholdPolicy::initialize

 5、SimpleThresholdPolicy::event

6、AdvancedThresholdPolicy method_invocation_event和method_back_branch_event

7、AdvancedThresholdPolicy call_event和loop_event

8、AdvancedThresholdPolicy::submit_compile


      在《Hotspot 热点代码编译和栈上替换 源码解析》中热点代码编译是通过InterpreterRuntime::frequency_counter_overflow方法完成,该方法实际通过CompilationPolicy:event触发方法编译,如果是需要栈上替换的方法则event方法返回包含编译代码的nmethod实例,否则返回NULL。本篇博客就顺着event方法的实现来研究表示编译策略的CompilationPolicy的实现。

1、定义

      CompilationPolicy的定义位于hotspot src/share/vm/runtime/compilationPolicy.hpp中,表示编译策略,即决定那个方法应该编译和用什么类型的编译器编译。CompilationPolicy继承自CHeapObj,其定义的属性比较简单,就三个,都是静态属性,如下:

  • _policy:CompilationPolicy*,运行时使用的CompilationPolicy
  • _accumulated_time:elapsedTimer,用于统计编译耗时
  • _in_vm_startup:bool,是否正在VM启动中

   CompilationPolicy定义的方法有两类,静态方法和实例的虚方法,静态方法都是工具类方法,如must_be_compiled,can_be_compiled,can_be_osr_compiled等判断某个方法能否编译;实例的虚方法都是通过CompilationPolicy::policy()获取运行时的CompilationPolicy实例来调用的,如disable_compilation用来禁用某个方法的编译,select_task方法用来从CompileQueue队列中选取一个编译任务CompileTask,重点关注其initialize方法和event方法的实现。

    CompilationPolicy的类继承关系如下:

 默认开启分层编译的情形下,默认采用AdvancedThresholdPolicy策略。

 2、compilationPolicy_init

     搜索CompilationPolicy的set_policy的调用链,结果如下:

即通过compilationPolicy_init方法完成CompilationPolicy的初始化,其源码实现如下:

其中COMPILER2表示启用C2编译器,TIERED表示启用分级编译,这两个都是x86_64下默认添加的宏。CompilationPolicyChoice表示编译策略选择,server模式下默认是3,可通过jinfo -flag CompilationPolicyChoice 31957查看,其中31957是进程ID。所以server模式下默认是选择AdvancedThresholdPolicy编译策略。

3、AdvancedThresholdPolicy

     AdvancedThresholdPolicy继承自SimpleThresholdPolicy,SimpleThresholdPolicy在CompilationPolicy的基础上添加了两个属性_c1_count, _c2_count,表示C1和C2编译线程的数量。AdvancedThresholdPolicy添加了_start_time和_increase_threshold_at_ratio两个属性,前者表示启动时间,后者表示当CodeCache已使用了指定比例时提升C1编译的阈值。

    AdvancedThresholdPolicy支持5个级别的编译,如下图:

   其中0,2,3三个级别下都会周期性的通知 AdvancedThresholdPolicy某个方法的方法调用计数即invocation counters and循环调用计数即backedge counters,不同级别下通知的频率不同。这些通知用来决定如何调整编译级别。

    某个方法刚开始执行时是通过解释器解释执行的,即level 0,然后AdvancedThresholdPolicy会综合如下两个因素和调用计数将编译级别调整为2或者3:

  1. C2编译任务队列的长度决定了下一个编译级别。据观察,level 2级别下的编译比level 3级别下的编译快30%,因此我们应该在只有已经收集了充分的profile信息后才采用level 3编译,从而尽可能缩短level 3级别下编译的耗时。当C2的编译任务队列太长的时候,如果选择level 3则编译会被卡住直到C2将之前的编译任务处理完成,这时如果选择Level 2编译则很快完成编译。当C2的编译压力逐步减小,就可以重新在level 3下编译并且开始收集profile信息。
  2. 根据C1编译任务队列的长度用来动态的调整阈值,在编译器过载时,会将已经编译完成但是不在使用的方法从编译队列中移除。

     当level 3下profile信息收集完成后就会转换成level 4了,可根据C2编译任务队列的长度来动态调整转换的阈值。当经过C1编译完成后,基于方法的代码块的数量,循环的数量等信息可以判断一个方法是否是琐碎(trivial)的,这类方法在C2编译下会产生和C1编译一样的代码,因此这时会用level 1的编译代替level 4的编译。

     当C1和C2的编译任务队列的长度足够短,也可在level 0下开启profile信息收集。编译队列通过优先级队列实现,每个添加到编译任务队列的方法都会周期的计算其在单位时间内增加的调用次数,每次从编译队列中获取任务时,都选择单位时间内调用次数最大的一个。基于此,我们也可以将那些编译完成后不再使用,调用次数不再增加的方法从编译任务队列中移除。跟编译级别转换相关的命令行参数及其转换逻辑可以参考AdvancedThresholdPolicy的注释。

      常见的各级别转换路径如下:

  • 0 -> 3 -> 4,最常见的转换路径,需要注意这种情况下profile可以从level 0开始,level3结束。
  • 0 -> 2 -> 3 -> 4,当C2的编译压力较大会发生这种情形,本来应该从0直接转换成3,为了减少编译耗时就从0转换成2,等C2的编译压力降低再转换成3
  • 0 -> (3->2) -> 4,这种情形下已经把方法加入到3的编译队列中了,但是C1队列太长了导致在0的时候就开启了profile,然后就调整了编译策略按level 2来编译,即时还是3的队列中,这样编译更快
  • 0 -> 3 -> 1 or 0 -> 2 -> 1,当一个方法被C1编译后,被标记成trivial的,因为这类方法C2编译的代码和C1编译的代码是一样的,所以不使用4,改成1
  • 0 -> 4,一个方法C1编译失败且一直在收集profile信息,就从0切换到4

4、AdvancedThresholdPolicy::initialize

      AdvancedThresholdPolicy::initialize就是AdvancedThresholdPolicy的初始化方法,其源码实现如下:

void AdvancedThresholdPolicy::initialize() {
  // Turn on ergonomic compiler count selection
  //CICompilerCountPerCPU默认值为false,表示是否每个CPU对应1个后台编译线程,CICompilerCount表示后台编译线程的数量,默认值3
  //当这两个都是默认值时,将CICompilerCountPerCPU置为true
  if (FLAG_IS_DEFAULT(CICompilerCountPerCPU) && FLAG_IS_DEFAULT(CICompilerCount)) {
    FLAG_SET_DEFAULT(CICompilerCountPerCPU, true);
  }
  int count = CICompilerCount;
  if (CICompilerCountPerCPU) {
    //考虑机器的CPU数量,计算总的后台编译线程数
    int log_cpu = log2_int(os::active_processor_count());
    int loglog_cpu = log2_int(MAX2(log_cpu, 1));
    count = MAX2(log_cpu * loglog_cpu, 1) * 3 / 2;
  }

  //计算C1,C2编译线程的数量
  set_c1_count(MAX2(count / 3, 1));
  set_c2_count(MAX2(count - c1_count(), 1));
  //重置CICompilerCount
  FLAG_SET_ERGO(intx, CICompilerCount, c1_count() + c2_count());

  // Some inlining tuning
#ifdef X86
  //InlineSmallCode表示只有当方法代码大小小于该值时才会使用内联编译的方式,x86下默认是1000
  //这里是将其调整为2000
  if (FLAG_IS_DEFAULT(InlineSmallCode)) {
    FLAG_SET_DEFAULT(InlineSmallCode, 2000);
  }
#endif

  set_increase_threshold_at_ratio();
  set_start_time(os::javaTimeMillis());
}

//IncreaseFirstTier
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值