Upload
nothingcosmos
View
4.175
Download
0
Embed Size (px)
DESCRIPTION
2012/06/16 x86opti4 nothingcosmos
Citation preview
x86 向け Hotspot の気持ちになって考える
JIT コンパイラの適応的
最適化について
Outline
1. JIT コンパイラの気持ちになって
2. 適応的最適化と上手に付き合う
3. Hotspot の x86 向け最適化2012/06/16 X86/X64 最適化勉強会 4
nothingcosmos <[email protected]>
プロフィール
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 コンパイラには無理だけど、代わりに手続き間最適化が可能。
※ リフレクションや動的コード生成 / 読み込みが無いからね。
コンパイル済み Asm
Runtime
プロファイル情報を通知
JIT コンパイルを指示
Hotspot の概要
適応的最適化の概要
Hotspot の特徴。何に適応するのか
(1) 実行時の CPU アーキテクチャに適応する。
x86 の SSE42 や AVX (2) 実行時の環境に適応する。
クラスローダーや読み込み済みのクラスを監視する。
(3) 実行するプログラムに適応する。
インタプリタ実行時にプロファイルを取る。
上記情報は、 JIT コンパイル時に活用して最適化する。
コンパイル済み Asm
Runtime
コンパイル済みのインストール or 破棄
クラスロードを通知
プロファイル情報を参照
プロファイル情報を通知
JIT コンパイルを指示
適応的最適化の概要
SSE 使うコードを生成
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 = ......P.invoke();// 仮想関数呼び出し...
interface Product { public void invoke();}
class ProductA{ public void invoke() { //... System.out.println("hello"); }}
Method inlining と脱仮想化について
Product P = ......P.invoke();// 仮想関数呼び出し...
interface Product { public void invoke();}
class ProductA{ public void invoke() { //... System.out.println("hello"); }}
呼び出し先が一意に決まる直接呼び出しに置換してインライン展開しちゃう。
Product P = ......{ //ProductA.invoke(); //... System.out.println("hello");}...
Direct devirtializationMethod 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();}
public class ProductA{ public void invoke() { //... System.out.println("hello"); }}
Interface を実装するクラスが複数あったら? public class ProductB{
public void invoke() { //... System.out.println(”world"); }}
クラスローダーが後から読み込んだら
コンパイル済み Asm
Runtime
C1 の脱仮想化について
Dependency に脱仮想化したメソッドを登録
仮想関数のクラス階層を解析
コンパイル済み Asm
Runtime
コンパイル済みのインストール or 破棄
クラスロードを通知
C1 の脱仮想化について
Dependency に脱仮想化したメソッドを登録
仮想関数のクラス階層を解析
Dependencyリストを走査
コンパイル済み Asm
Runtime
コンパイル済みのインストール or 破棄
クラスロードを通知
Shark の脱仮想化について
Dependency に脱仮想化したメソッドを登録
仮想関数のクラス階層を解析
Dependencyリストを走査
Shark LLVM
Bytecode から LLVM Bitcode に変換する際に、Invokevirtual を直接関数呼び出しに変換したり
GC 用の safepoint やexceptionhandling を bitcode に挿入
Hotspot の JIT コンパイラ
AbstractCompiler
C2CompilerC1Compiler
SharkCompiler
C2 の脱仮想化について
C2 の脱仮想化は、 guarded deviratualization
ガード (nullcheck, typecheck) を挿入して脱仮想化を試行する。
ガードの else 節で脱最適化もしくは仮想関数呼び出しを挿入
そのため、 C1 と異なりクラスローダで読み込むのは大丈夫。
ガードの else 節の実行に注意。
C2 の脱仮想化について
インタプリタは P>invoke() で何を呼び出したのか記録する。
Interface Product を実装したクラスを解析する。
呼び出し候補が 1種。 moromorphic call
呼び出し候補が 2種。 bimorphic call
呼び出し候補が 3種以上。 megamorphic call
Product P = xxx;…...P->invoke();//interface越しに仮想関数呼び出し//ProductA が 100 回、 ProductB が 10 回、、?
C2 の脱仮想化について
Monomorphic call 呼び出し候補が1つだけ。呼び出し履歴が1種だけ。
mono_morphic = P->invoke;if (nullcheck(mono_morhpic) && typecheck(invokeA, mono_morphic)) { //invokeA() // インライン展開を試行する …} else { Uncommon_trap();// 脱最適化を試行する。}
C2 の脱仮想化について
Bimorphic 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 の脱仮想化について
Megamorphic call
呼び出し候補が 3 つ以上。呼び出し履歴が 3種以上。
//invokeA が 90% の確率で呼び出される場合に限るmega_morphic = P->invoke;if (nullcheck(mega_morphic) && typecheck(invokeA, mega_morphic)) { //invokeA(); // インライン展開を試行する ...} else { P->invoke();//vtable を引いて仮想関数を呼び出す}
C2 の脱仮想化について
Megamorphic 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 You're Not Looking)
JIT コンパイラの生成したコード
JVM が JIT コンパイルしたアセンブラを読むのは
結構おもろい
OpenJDK を debug版でビルドしてチャレンジ
XX:+PrintOptoAssembly
public static long testCompare() { 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; }
C2 で JIT コンパイル
52byte
脱最適化ゾーン
共通のループ前準備
ガチガチチェックの1週しか回らないループ
真のカーネルループ
後処理ループ。カーネルループと変わらん。
OSR の後処理
Hotspot の x86 向け最適化
JVM の低レイヤのお仕事。バイナリアン向け。
openjdk7/hotspot/src/cpu/x86/vm
MacroAssembler で書かれてる。 assembler_x86.cpp
専用の vmIntrinsics を用意して x86 向けに最適化
Prefetch の自動挿入
Cmpxchg で cas してみたり
文字列処理を自動で SSE42 使って高速化
Hotspot の x86 向け最適化
invokevirtualString.equals(String)
MacroAssemblerchar_arrays_equals()
ptest命令使ってsimd
invokevirtualvmIntrinsics(string_equals)
callNode(StrEqualsNode)
bytecode
jvm の中
IdealNode
MachNode
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("%d\n", sum);
149 B28: # B47 B29 <- B35 B27 Freq: 0.64794149 MOV ECX, Thread::current()155 MOV EAX,[ECX + #68]158 LEA EBX,[EAX + #16]15b MOV EDI,java/lang/Class:exact *160 MOV EDI,[EDI + #108] ! Field java/lang/System.out163 CMPu EBX,[ECX + #76]166 Jnb,u B47 P=0.000100 C=-1.00000016616c B29: # B30 <- B28 Freq: 0.64787516c MOV [ECX + #68],EBX16f PREFETCHNTA [EBX + #192] 176 MOV [EAX],0x0000000117c PREFETCHNTA [EBX + #256] 183 MOV [EAX + #4],precise klass [Ljava/lang/Object;:18a PREFETCHNTA [EBX + #320] 191 MOV [EAX + #8],#1198 PREFETCHNTA [EBX + #384] 19f MOV [EAX + #12],#01a6 MOV [ESP + #16],EDI
昔は Prefetcht0 を自動挿入する
ケースもあったような気が。
文字列系の処理は、 prefetchnta
でキャッシュ汚染を最小化
Prefetchnta は L1 キャッシュのみ
他のキャッシュには乗せない。
String.indexOf(String)
void MacroAssembler::string_indexof(... // Scan string for start of substr in 16-byte vectors bind(SCAN_TO_SUBSTR); assert(cnt1 == rdx && cnt2 == rax && tmp == rcx, "pcmpestri"); pcmpestri(vec, Address(result, 0), 0x0d); jccb(Assembler::below, FOUND_CANDIDATE); // CF == 1 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);
bind(ADJUST_STR); cmpl(cnt1, 8); // Do not read beyond string jccb(Assembler::greaterEqual, SCAN_TO_SUBSTR);
...
pcmpestri の mode=0xd
1100:部分文字列比較
01:unsigned short比較
string_indexofC8 っていう
特殊版もある。
herumi さんの strstr を
参照。
indexOf(int) ではない。
String.compareTo(String)
void MacroAssembler::string_compare(… int stride = 8;... bind(COMPARE_WIDE_VECTORS); movdqu(vec1, Address(str1, result, scale)); pcmpestri(vec1, Address(str2, result, scale), pcmpmask); // After pcmpestri cnt1(rcx) contains mismatched element index
jccb(Assembler::below, VECTOR_NOT_EQUAL); // CF==1 addptr(result, stride); subptr(cnt2, stride); jccb(Assembler::notZero, COMPARE_WIDE_VECTORS);
// compare wide vectors tail testl(result, result); jccb(Assembler::zero, LENGTH_DIFF_LABEL);...
pcmpestri の mode=019
010000:negative polarity
1000:equal each
01:unsigned short比較
herumi さんの SSE4.2
文字列処理を参照。
返り値は 0, >0, <0 の
いずれかなので、
出口は多少複雑。
String.equals(String), Arrays.equals()// Compare char[] arrays aligned to 4 bytes or substrings.void MacroAssembler::char_arrays_equals(… shll(limit, 1); // byte count != 0 movl(result, limit); // copy // Compare 16-byte vectors andl(result, 0x0000000e); // tail count (in bytes) andl(limit, 0xfffffff0); // vector count (in bytes) jccb(Assembler::zero, COMPARE_TAIL);
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); jccb(Assembler::notZero, FALSE_LABEL); addptr(limit, 16); jcc(Assembler::notZero, COMPARE_WIDE_VECTORS);
pxor, ptest を使って、
128bit単位で等しいか判定
String でも、 Arrays でも、
左記の SSE42 が呼ばれる。
最後に端数を比較する。
testl(result, result); jccb(Assembler::zero, TRUE_LABEL);
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
charArrayEquals
0.826 0.446 0.38
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 が増えたり