概要
- なんとかJavaアプリケーションの一部メソッドを少ない暖気でC2コンパイルできないかと思い、CompileThresholdScalingを試してみた
- 実際に検証すると対象のメソッドが少ない暖気でC2コンパイルまで行えたが、すぐにDecompileされてしまった
JavaのJITコンパイルについて
以下の記事や登壇資料が非常に勉強になったので、そちらをまずは見てください。
やったこと
Kubernetesのdeployment.yamlの環境変数のJAVA_TOOL_OPTIONSに以下を指定しました。(メソッド名は実際に指定したものではないです)
- name: JAVA_TOOL_OPTIONS
value: >-
- XX:CompileCommand=CompileThresholdScaling,com.hoge.elasticsearch.*::*,0.01
- CompileCommand: JVMのJITコンパイラの動作を特定のメソッドやクラスに対してカスタマイズするための命令
- CompileThresholdScaling: メソッドがJITコンパイルされるまでのインタープリタでの実行回数の閾値を調整するためのオプション
今回はcom.hoge.elasticsearchパッケージ配下の全クラスの全メソッドを対象に1%未満のリクエストでC2コンパイルされるかを試してみます。
結果
以下のログが出力されました。
ログが長いですが、count=57でc2コンパイルされているので、57回叩かれただけでサーバコンパイルされました。
<nmethod compile_id='32021' compiler='c2' level='4' entry='0x00007fb7f708bfc0' size='3384' address='0x00007fb7f708bd90' relocation_offset='352' insts_offset='560' stub_offset='1392' scopes_data_offset='1744' scopes_pcs_offset='2336' dependencies_offset='3072' handler_table_offset='3128' nul_chk_table_offset='3344' oops_offset='1536' metadata_offset='1576' method='com.hoge.elasticsearch execute (Lcom/hoge/elasticsearch;)Ljava/util/Map;' bytes='151' count='57' iicount='57' stamp='391.528'/>
これにてめでたしめでたしと思いましたがそうはいきませんでした。
続きのログを見てみると、以下のようなログがありました。
<uncommon_trap thread='5009' reason='bimorphic_or_optimized_type_check' action='maybe_recompile' debug_id='0' compile_id='32021' compiler='c2' level='4' count='2' state='bimorphic_or_optimized_type_check' stamp='521.913'>
<jvms bci='59' method='com.hoge execute (Lcom/hoge;)Ljava/util/Map;' bytes='151' count='57' iicount='57' bimorphic_or_optimized_type_check_traps='2'/>
</uncommon_trap>
uncommon_trapと書かれており、CompilethresholdScalingで指定したメソッドがなにかチェックされていました。これが4回ほど続いています。
更にログを見ると、対象のメソッドがlevel2まで下がってしまいました。deconpile=1となっていることからlevel4までコンパイルされたメソッドはlevel2までdecompileされたようです。
<nmethod compile_id='34087' compiler='c1' level='2' entry='0x00007fb7efbadde0' size='5736' address='0x00007fb7efbadb10' relocation_offset='352' insts_offset='720' stub_offset='2768' scopes_data_offset='3192' scopes_pcs_offset='3888' dependencies_offset='5008' handler_table_offset='5032' nul_chk_table_offset='5680' oops_offset='3088' metadata_offset='3112' method='com.hoge execute (Lcom/hoge;)Ljava/util/Map;' bytes='151' count='136' iicount='136' decompiles='1' bimorphic_or_optimized_type_check_traps='4' stamp='523.307'/>
uncommon_trapのログを見るとreasonが bimorphic_or_optimized_type_check
となっています。
uncommon_trapは以下のRedHatさんの記事にヒントが有りました。
But then, a curious thing happens: there is no second branch code at all! Instead, there is a call to so called "uncommon trap"! That one is the notification to runtime that we have failed the compilation condition, and this "uncommon" branch is now taken.
コンパイル条件が失敗した際にランタイムに通知されるものらしい。
OpenJDKのコードも見てみる
自分はOpenJDKのコードを読んだことがないが、該当するOpenJDKのコードを読んでみる。
まずreasonのbimorphic_or_optimized_type_check
はsrc/hotspot/share/runtime/deoptimization.cpp にありました。
const char* Deoptimization::_trap_reason_name[] = {
// Note: Keep this in sync. with enum DeoptReason.
"none",
"null_check",
"null_assert" JVMCI_ONLY("_or_unreached0"),
"range_check",
"class_check",
"array_check",
"intrinsic" JVMCI_ONLY("_or_type_checked_inlining"),
"bimorphic" JVMCI_ONLY("_or_optimized_type_check"),
"profile_predicate",
"unloaded",
"uninitialized",
"initialized",
"unreached",
"unhandled",
"constraint",
"div0_check",
"age",
"predicate",
"loop_limit_check",
"speculate_class_check",
"speculate_null_check",
"speculate_null_assert",
"unstable_if",
"unstable_fused_if",
"receiver_constraint",
#if INCLUDE_JVMCI
"aliasing",
"transfer_to_interpreter",
"not_compiled_exception_handler",
"unresolved",
"jsr_mismatch",
#endif
今回ログに出ていた"bimorphic" JVMCI_ONLY("_or_optimized_type_check"
もありました。
ただここからどうチェックしてdecompileされるのかまでは深掘りができていないのでまた別の記事に書く予定です。
まとめ
今回は少ない暖気で一部メソッドをC2コンパイルまで持っていけないかを試してみましたが、decompileされるという結果になりました。
JavaやOpenJDK,JIT周りはまだまだ知見が足りないので、もっと勉強をする必要があると今回の検証でより強く感じました。