• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Adaptive optimization of JIT compiler
 

Adaptive optimization of JIT compiler

on

  • 4,104 views

2012/06/16 x86opti4 nothingcosmos

2012/06/16 x86opti4 nothingcosmos

Statistics

Views

Total Views
4,104
Views on SlideShare
2,791
Embed Views
1,313

Actions

Likes
5
Downloads
22
Comments
1

4 Embeds 1,313

http://nothingcosmos.blog52.fc2.com 1197
http://control.blog.fc2.com 112
https://twitter.com 3
http://nothingcosmos.blog52.fc2blog.net 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • 補足です。
    ベンチマークは間違えてOpenJDK7のDebug版で取っていたようなので、仮想関数呼び出しのオーバーヘッドは3400clockは遅すぎます。1000~1700clockくらいが妥当ではないでしょうか。
    また、OpenJDK8のC2コンパイラに関する記述に誤りがあるようです。
    当方はjdk7からソースコードをチェックアウトしていたのですが、チェックアウト先URLの最新はjdk7uのようです。jdk7は2011年で更新が止まっており、
    最新のOpenJDK7には、OpenJDK8と同様のC2向け各種最適化が既に入っているようです。
    また、質疑応答でJVMのベクトル最適化であるsuperwordがデフォルトでfalseだと回答しましたが、
    最新ではデフォルトtrueのようです。
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Adaptive optimization of JIT compiler Adaptive optimization of JIT compiler Presentation Transcript

    • x86 向け Hotspot の気持ちになって考える JIT コンパイラの適応的 最適化についてOutline1. JIT コンパイラの気持ちになって2. 適応的最適化と上手に付き合う3. Hotspot の x86 向け最適化 2012/06/16 X86/X64 最適化勉強会 4 nothingcosmos <nothingcosmos@gmail.com>
    • プロフィール x86 最適化勉強会は 1 回目以来 (icc を使う ) ソフトウェアエンジニア (SI 系 ) Excel がともだち 昔コンパイラを作る仕事をしていました。 ここ 1 年は JIT コンパイラを搭載した VM に興味が向いてました。 LLVM­­>Hotspot(OpenJDK)­­>V8­­>dartvm 決して Oracle の回し者ではありませんので、、
    • 注意点 JIT コンパイラの話は薀蓄程度のものです。 JIT コンパイラとともだちになっても、あまり効果ない。 GC の話は無いです。 JVM のパフォーマンスは GC のチューニングに依存。 Oracle さんの GC セミナーとか勉強になるんじゃ
    • 用語説明 用語説明 Hotspot  JVM の実装。今回は OpenJDK7 を指します。 Client コンパイラ [­client]  C1 Server コンパイラ [­server]  C2 opto Devirtualization  脱仮想化 仮想関数呼び出しを置き換える。 Deoptimization  脱最適化 インタプリタ実行に戻す。
    • JIT コンパイラの概要 const なんとかさんが、なんとかしてくれる疑惑。
    • Hotspot の概要 最初は bytecode をインタプリタ実行する。 条件にマッチした method を JIT コンパイル  (1)  よく回るループを内包した method backword branch count  (2)  よく呼び出される method method invocation count JIT コンパイラは、実行時の定常状態 (steady state) を、 測定、仮定し、最適化する。 AOT コンパイラには無理だけど、代わりに手続き間最適化が可能。 ※ リフレクションや動的コード生成 / 読み込みが無いからね。
    • Hotspot の概要 JIT コンパイル プロファイル情報 を指示 を通知 Runtimeコンパイル済み Asm
    • 適応的最適化の概要 Hotspot の特徴。何に適応するのか (1) 実行時の CPU アーキテクチャに適応する。  x86 の SSE42 や AVX (2) 実行時の環境に適応する。  クラスローダーや読み込み済みのクラスを監視する。 (3) 実行するプログラムに適応する。  インタプリタ実行時にプロファイルを取る。 上記情報は、 JIT コンパイル時に活用して最適化する。
    • 適応的最適化の概要 プロファイル情報 を参照 JIT コンパイル プロファイル情報 を指示 を通知 Runtimeクラスロードを 通知 コンパイル済みのインストール or 破棄 SSE 使うコード を生成 コンパイル済み Asm
    • JIT コンパイラの気持ちになって JavaOne Tokyo 2012  How to Write Low Latency Java Applications  What you need to know about JIT compilation ポイント  Optimization impact from "method inlining"
    • JIT コンパイラの気持ちになって JavaOne Tokyo 2012  How to Write Low Latency Java Applications  What you need to know about JIT compilation ポイント  Optimization impact from "method inlining"結論●● JIT コンパイラのことなんて気にしなくていいよ(^^;● 後でボトルネック調査して最適化しろ
    • Method inlining と脱仮想化について Product P = ... interface Product { ... public void invoke(); P.invoke();// 仮想関数呼び出し } ...class ProductA{ public void invoke() { //... System.out.println("hello"); }}
    • Method inlining と脱仮想化について Product P = ... interface Product { ... public void invoke(); P.invoke();// 仮想関数呼び出し } ... 呼び出し先が一意に決まる 直接呼び出しに置換して インライン展開しちゃう。class ProductA{ public void invoke() { Product P = ... //... ... System.out.println("hello"); { //ProductA.invoke(); } //...} Direct devirtialization System.out.println("hello"); Method inlining } ...
    • Hotspot の direct devirtualization bytecode レベルで、 invokevirtual もしくは invokeinterface で、 呼び出し候補が 1 つだけなら、  invokevirtual or invokeinterface­­>invokespecial に置換  親クラスが abstract だったらアウト。  invokestatic 、 invokespecial 、 invokevirtual かつ final だったら inline 展開を試行 投機的に実行する場合、 dependency で条件 (assert_unique_concrete_method) を登録 Classloader が interface Product を継承する他のクラスを読み 込んだら、 mutex で VM 全体を止めて脱最適化を行う。
    • Method inlining と脱仮想化について public interface Product { public void invoke(); } クラスローダーが 後から読み込んだら Interface を 実装するクラスが 複数あったら? public class ProductB{ public void invoke() { //... System.out.println(”world"); public class ProductA{ } public void invoke() { } //... System.out.println("hello"); } }
    • C1 の脱仮想化について Dependency に 脱仮想化した メソッドを登録 仮想関数の クラス階層を解析 Runtime コンパイル済み Asm
    • C1 の脱仮想化について Dependency Dependency に リストを走査 脱仮想化した メソッドを登録 仮想関数の クラス階層を解析 Runtimeクラスロードを 通知 コンパイル済みのインストール or 破棄 コンパイル済み Asm
    • Shark の脱仮想化について Dependency Dependency に リストを走査 脱仮想化した メソッドを登録 仮想関数の クラス階層を解析 Runtimeクラスロードを 通知 Shark LLVM Bytecode から LLVM Bitcode に変換する際に、 Invokevirtual を直接関数呼び出しに変換したり コンパイル済みの GC 用の safepoint やインストール or 破棄 exceptionhandling を bitcode に挿入 コンパイル済み Asm
    • Hotspot の JIT コンパイラ AbstractCompilerC1Compiler SharkCompiler C2Compiler
    • C2 の脱仮想化について C2 の脱仮想化は、 guarded deviratualization ガード (nullcheck, typecheck) を挿入して脱仮想化を試行する。 ガードの else 節で脱最適化もしくは仮想関数呼び出しを挿入 そのため、 C1 と異なりクラスローダで読み込むのは大丈夫。  ガードの else 節の実行に注意。
    • C2 の脱仮想化について Product P = xxx; … ... P->invoke();//interface 越しに仮想関数呼び出し //ProductA が 100 回、 ProductB が 10 回、、? インタプリタは P­>invoke() で何を呼び出したのか記録する。 Interface Product を実装したクラスを解析する。 呼び出し候補が 1 種。 moro­morphic call 呼び出し候補が 2 種。 bi­morphic call 呼び出し候補が 3 種以上。 mega­morphic call
    • C2 の脱仮想化について  Mono­morphic call  呼び出し候補が1つだけ。呼び出し履歴が1種だけ。mono_morphic = P->invoke;if (nullcheck(mono_morhpic) && typecheck(invokeA, mono_morphic)) { //invokeA() // インライン展開を試行する …} else { Uncommon_trap();// 脱最適化を試行する。}
    • C2 の脱仮想化について  Bi­morphic call  呼び出し候補が 2 つ。呼び出し履歴が 2 種だけ。bi_morphic = P->invoke;if (nullcheck(bi_morphic) && typecheck(invokeA, bi_morphic)) { //invokeA();// インライン展開を試行する ...} else if(nullcheck(bi_morphic) && typecheck(invokeB, bi_morphic) { invokeB();// インライン展開を試行しない。} else { Uncommon_trap();// 脱最適化を試行する。}
    • C2 の脱仮想化について  Mega­morphic call  呼び出し候補が 3 つ以上。呼び出し履歴が 3 種以上。//invokeA が 90% の確率で呼び出される場合に限るmega_morphic = P->invoke;if (nullcheck(mega_morphic) && typecheck(invokeA, mega_morphic)) { //invokeA(); // インライン展開を試行する ...} else { P->invoke();//vtable を引いて仮想関数を呼び出す}
    • C2 の脱仮想化について  Mega­morphic call  呼び出し候補が 3 つ以上。呼び出し履歴が 3 種以上。// プロファイルの結果、呼び出し先に偏りがないP->invoke();//vtable を引いて仮想関数を呼び出す
    • 脱仮想化のベンチマーク結果 10 割で呼ばれるメソッドと、 3 種 3 割で均等に呼ばれる仮想関数 呼び出しを 1000,000 回繰り返す。 10 割で呼ばれるメソッド、 0.24 秒 3 割で均等に呼ばれる仮想関数 1.2 秒 Corei7 で 3.4GHz なので、仮想関数呼び出しは 1 回あたり 3400clock 程度を消費している?
    • 適応的最適化と上手に付き合う? JIT コンパイラは頑張ってくれてるけど、俺はどうすれば??? 目的の関数が inline 展開されているか確認する。  処理のボトルネックに仮想関数呼び出しを使わない。  インスタンス化するのは一種類までに絞る。  JIT コンパイルされた最良なコードが実行されているか trace する。 目的の関数が JIT コンパイルされているか確認する。 jdk1.5 とか結構はまる。 Oracle さんにサポートツール沢山あるんじゃないかな?
    • オプションで確認する。 ­XX:+UnlockDiagnosticVMOptions ­XX:+PrintInlining ­XX:+PrintCompilation ­XX:+PrintOptoAssembly JVM のオプションは下記スライドが詳しい。 Øredev 2011 ­ JVM JIT for Dummies (What the JVM Does  With Your Bytecode When Youre Not Looking)
    • JIT コンパイラの生成したコード  JVM が JIT コンパイルしたアセンブラを読むのは 結構おもろい  OpenJDK を debug 版でビルドしてチャレンジ ­XX:+PrintOptoAssembly  C2 で JIT コンパイルpublic static long testCompare() { 52byte long sum=0; String base = new String("abcdefghijklmnopqrstuvwxyz5555"); for (int i=0; i<TEST_LENGTH; i++) { sum += base.compareTo(sarray.get(i%INIT_LENGTH)); } return sum;}
    • 共通のループ前準備 OSR の後処理 ガチガチチェックの 1 週しか回らないループ 真のカーネルループ 後処理ループ。カーネルループと変わらん。 脱最適化ゾーン
    • Hotspot の x86 向け最適化 JVM の低レイヤのお仕事。バイナリアン向け。 openjdk7/hotspot/src/cpu/x86/vm MacroAssembler で書かれてる。 assembler_x86.cpp 専用の vmIntrinsics を用意して x86 向けに最適化  Prefetch の自動挿入  Cmpxchg で cas してみたり  文字列処理を自動で SSE42 使って高速化
    • Hotspot の x86 向け最適化  bytecode invokevirtual String.equals(String) invokevirtual  jvm の中 vmIntrinsics(string_equals)  IdealNode callNode(StrEqualsNode) MacroAssembler  MachNode char_arrays_equals() ptest 命令使って simd
    • Hotspot の x86 向け最適化 src/share/vm/opto の下の見どころ  macro.cpp   メモリアロケーションや TLAB や lock src/cpu/x86/vm の下の見どころ  MacroAssembler::string_indexof/string_indexofC8  MacroAssembler::string_compare  MacroAssembler::char_arrays_equals  MacroAssembler::biased_locking_enter  MacroAssembler::g1_write_barrier_pre/post
    • prefetch の自動挿入System.out.printf("%dn", sum);149 B28: # B47 B29 <- B35 B27 Freq: 0.64794149 MOV ECX, Thread::current()155 MOV EAX,[ECX + #68]158 LEA EBX,[EAX + #16]  昔は Prefetcht0 を自動挿入する15b MOV EDI,java/lang/Class:exact *160 MOV EDI,[EDI + #108] ! Field java/lang/System.out ケースもあったような気が。163 CMPu EBX,[ECX + #76]166 Jnb,u B47 P=0.000100 C=-1.000000  文字列系の処理は、 prefetchnta16616c B29: # B30 <- B28 Freq: 0.64787516c MOV [ECX + #68],EBX でキャッシュ汚染を最小化16f PREFETCHNTA [EBX + #192]176 MOV [EAX],0x0000000117c PREFETCHNTA [EBX + #256]183 MOV [EAX + #4],precise klass [Ljava/lang/Object;:  Prefetchnta は L1 キャッシュのみ18a PREFETCHNTA [EBX + #320]191 MOV [EAX + #8],#1198 PREFETCHNTA [EBX + #384] 他のキャッシュには乗せない。19f MOV [EAX + #12],#01a6 MOV [ESP + #16],EDI
    • String.indexOf(String)  pcmpestri の mode=0xdvoid MacroAssembler::string_indexof(... // Scan string for start of substr in 16-byte vectors 1100: 部分文字列比較 bind(SCAN_TO_SUBSTR); assert(cnt1 == rdx && cnt2 == rax && tmp == rcx, "pcmpestri");     01:unsigned short 比較 pcmpestri(vec, Address(result, 0), 0x0d); jccb(Assembler::below, FOUND_CANDIDATE); // CF == 1  string_indexofC8 っていう subl(cnt1, 8); jccb(Assembler::lessEqual, RET_NOT_FOUND); // Scanned full string cmpl(cnt1, cnt2); 特殊版もある。 jccb(Assembler::negative, RET_NOT_FOUND); // Left less then substring addptr(result, 16);  herumi さんの strstr を bind(ADJUST_STR); 参照。 cmpl(cnt1, 8); // Do not read beyond string jccb(Assembler::greaterEqual, SCAN_TO_SUBSTR);  indexOf(int) ではない。...
    • String.compareTo(String)  pcmpestri の mode=019void MacroAssembler::string_compare(… int stride = 8; 010000:negative polarity... bind(COMPARE_WIDE_VECTORS);     1000:equal each movdqu(vec1, Address(str1, result, scale)); pcmpestri(vec1, Address(str2, result, scale), pcmpmask);         01:unsigned short 比較 // After pcmpestri cnt1(rcx) contains mismatched element index jccb(Assembler::below, VECTOR_NOT_EQUAL); // CF==1  herumi さんの SSE4.2 addptr(result, stride); subptr(cnt2, stride); 文字列処理を参照。 jccb(Assembler::notZero, COMPARE_WIDE_VECTORS); // compare wide vectors tail  返り値は 0, >0, <0 の testl(result, result); jccb(Assembler::zero, LENGTH_DIFF_LABEL); いずれかなので、... 出口は多少複雑。
    • String.equals(String), Arrays.equals()// Compare char[] arrays aligned to 4 bytes or substrings.void MacroAssembler::char_arrays_equals(…  pxor, ptest を使って、 shll(limit, 1); // byte count != 0 movl(result, limit); // copy 128bit 単位で等しいか判定 // Compare 16-byte vectors andl(result, 0x0000000e); // tail count (in bytes) andl(limit, 0xfffffff0); // vector count (in bytes)  String でも、 Arrays でも、 jccb(Assembler::zero, COMPARE_TAIL); 左記の SSE42 が呼ばれる。 lea(ary1, Address(ary1, limit, Address::times_1)); lea(ary2, Address(ary2, limit, Address::times_1)); negptr(limit);  最後に端数を比較する。 bind(COMPARE_WIDE_VECTORS); movdqu(vec1, Address(ary1, limit, Address::times_1)); movdqu(vec2, Address(ary2, limit, Address::times_1)); pxor(vec1, vec2); ptest(vec1, vec1); testl(result, result); jccb(Assembler::notZero, FALSE_LABEL); jccb(Assembler::zero, TRUE_LABEL); addptr(limit, 16); jcc(Assembler::notZero, COMPARE_WIDE_VECTORS); movdqu(vec1, Address(ary1, result, Address::times_1, -16)); movdqu(vec2, Address(ary2, result, Address::times_1, -16)); pxor(vec1, vec2); ptest(vec1, vec1); jccb(Assembler::notZero, FALSE_LABEL); jmpb(TRUE_LABEL); ...
    • ベンチマーク結果 SSE42 の有無を適当にベンチマーク ( ループを 100M 回 ) ループのオーバーヘッドは取り除いています。 データが適当なので、数値の信頼性ないですが雰囲気だけ。 SSE42 なし SSE42 あり 差 (sec) compareTo 2.421 1.156 1.265 indexOf(String) 2.433 1.186 1.247 indexOf(int) 1.31 1.327 -0.017 indexOfC8 2.042 0.857 1.185 charArrayEqual 0.826 0.446 0.38 s String.equals -0.001 0.002 -0.003
    • おまけ (OpenJDK8) OS 対応  BSD へ対応 CPU 依存部分  AVX へ対応  高速な macro を追加 fast_pow(), fast_exp()  FPInstruction を使って計算した log を元に pow と exp C2 コンパイラ  OptimizePtrCompare オプションが追加。デフォルト true  EscapeAnalysis が大幅改良。 PtrCompare でごにょ  nest した lock/unlock の除去。 MemBarNode が増えたり