adaptive optimization of jit compiler

39
x86 向け Hotspot の気持ちになって考える JIT コンパイラの適応的 最適化について Outline 1. JIT コンパイラの気持ちになって 2. 適応的最適化と上手に付き合う 3. Hotspot x86 向け最適化 2012/06/16 X86/X64 最適化勉強会 4 nothingcosmos <[email protected]>

Upload: nothingcosmos

Post on 27-May-2015

4.172 views

Category:

Technology


0 download

DESCRIPTION

2012/06/16 x86opti4 nothingcosmos

TRANSCRIPT

Page 1: Adaptive optimization of JIT compiler

x86 向け Hotspot の気持ちになって考える

JIT コンパイラの適応的

最適化について

Outline

1. JIT コンパイラの気持ちになって

2. 適応的最適化と上手に付き合う

3. Hotspot の x86 向け最適化2012/06/16 X86/X64 最適化勉強会 4

nothingcosmos <[email protected]>

Page 2: Adaptive optimization of JIT compiler

プロフィール

x86 最適化勉強会は 1 回目以来 (icc を使う ) ソフトウェアエンジニア (SI 系 ) Excel がともだち

昔コンパイラを作る仕事をしていました。

ここ 1 年は JIT コンパイラを搭載した

VM に興味が向いてました。

LLVM­­>Hotspot(OpenJDK)­­>V8­­>dartvm

決して Oracle の回し者ではありませんので、、

Page 3: Adaptive optimization of JIT compiler

注意点

JIT コンパイラの話は薀蓄程度のものです。

JIT コンパイラとともだちになっても、あまり効果ない。

GC の話は無いです。

JVM のパフォーマンスは GC のチューニングに依存。

Oracle さんの GC セミナーとか勉強になるんじゃ

Page 4: Adaptive optimization of JIT compiler

用語説明

用語説明

Hotspot  JVM の実装。今回は OpenJDK7 を指します。

Client コンパイラ [­client]  C1

Server コンパイラ [­server]  C2 opto

Devirtualization  脱仮想化 仮想関数呼び出しを置き換える。

Deoptimization  脱最適化 インタプリタ実行に戻す。

Page 5: Adaptive optimization of JIT compiler

JIT コンパイラの概要

const なんとかさんが、なんとかしてくれる疑惑。

Page 6: Adaptive optimization of JIT compiler

Hotspot の概要

最初は bytecode をインタプリタ実行する。

条件にマッチした method を JIT コンパイル

(1)  よく回るループを内包した method

backword branch count (2)  よく呼び出される method

method invocation count JIT コンパイラは、実行時の定常状態 (steady state) を、

測定、仮定し、最適化する。

AOT コンパイラには無理だけど、代わりに手続き間最適化が可能。

※ リフレクションや動的コード生成 / 読み込みが無いからね。

Page 7: Adaptive optimization of JIT compiler

コンパイル済み Asm

Runtime

プロファイル情報を通知

JIT コンパイルを指示

Hotspot の概要

Page 8: Adaptive optimization of JIT compiler

適応的最適化の概要

Hotspot の特徴。何に適応するのか

(1) 実行時の CPU アーキテクチャに適応する。

x86 の SSE42 や AVX (2) 実行時の環境に適応する。

クラスローダーや読み込み済みのクラスを監視する。

(3) 実行するプログラムに適応する。

インタプリタ実行時にプロファイルを取る。

上記情報は、 JIT コンパイル時に活用して最適化する。

Page 9: Adaptive optimization of JIT compiler

コンパイル済み Asm

Runtime

コンパイル済みのインストール or 破棄

クラスロードを通知

プロファイル情報を参照

プロファイル情報を通知

JIT コンパイルを指示

適応的最適化の概要

SSE 使うコードを生成

Page 10: Adaptive optimization of JIT compiler

JIT コンパイラの気持ちになって

JavaOne Tokyo 2012

How to Write Low Latency Java Applications What you need to know about JIT compilation

ポイント

Optimization impact from "method inlining"

Page 11: Adaptive optimization of JIT compiler

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 コンパイラのことなんて気にしなくていいよ(^^;● 後でボトルネック調査して最適化しろ

Page 12: Adaptive optimization of JIT compiler

Method inlining と脱仮想化について

Product P = ......P.invoke();// 仮想関数呼び出し...

interface Product { public void invoke();}

class ProductA{ public void invoke() { //... System.out.println("hello"); }}

Page 13: Adaptive optimization of JIT compiler

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

Page 14: Adaptive optimization of JIT compiler

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 全体を止めて脱最適化を行う。

Page 15: Adaptive optimization of JIT compiler

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"); }}

クラスローダーが後から読み込んだら

Page 16: Adaptive optimization of JIT compiler

コンパイル済み Asm

Runtime

C1 の脱仮想化について

Dependency に脱仮想化したメソッドを登録

仮想関数のクラス階層を解析

Page 17: Adaptive optimization of JIT compiler

コンパイル済み Asm

Runtime

コンパイル済みのインストール or 破棄

クラスロードを通知

C1 の脱仮想化について

Dependency に脱仮想化したメソッドを登録

仮想関数のクラス階層を解析

Dependencyリストを走査

Page 18: Adaptive optimization of JIT compiler

コンパイル済み Asm

Runtime

コンパイル済みのインストール or 破棄

クラスロードを通知

Shark の脱仮想化について

Dependency に脱仮想化したメソッドを登録

仮想関数のクラス階層を解析

Dependencyリストを走査

Shark LLVM

Bytecode から LLVM Bitcode に変換する際に、Invokevirtual を直接関数呼び出しに変換したり

GC 用の safepoint やexceptionhandling を bitcode に挿入

Page 19: Adaptive optimization of JIT compiler

Hotspot の JIT コンパイラ

AbstractCompiler

C2CompilerC1Compiler

SharkCompiler

Page 20: Adaptive optimization of JIT compiler

C2 の脱仮想化について

C2 の脱仮想化は、 guarded deviratualization

ガード (nullcheck, typecheck) を挿入して脱仮想化を試行する。

ガードの else 節で脱最適化もしくは仮想関数呼び出しを挿入

そのため、 C1 と異なりクラスローダで読み込むのは大丈夫。

ガードの else 節の実行に注意。

Page 21: Adaptive optimization of JIT compiler

C2 の脱仮想化について

インタプリタは P­>invoke() で何を呼び出したのか記録する。

Interface Product を実装したクラスを解析する。

呼び出し候補が 1種。 moro­morphic call

呼び出し候補が 2種。 bi­morphic call

呼び出し候補が 3種以上。 mega­morphic call

Product P = xxx;…...P->invoke();//interface越しに仮想関数呼び出し//ProductA が 100 回、 ProductB が 10 回、、?

Page 22: Adaptive optimization of JIT compiler

C2 の脱仮想化について

Mono­morphic call 呼び出し候補が1つだけ。呼び出し履歴が1種だけ。

mono_morphic = P->invoke;if (nullcheck(mono_morhpic) && typecheck(invokeA, mono_morphic)) { //invokeA() // インライン展開を試行する …} else { Uncommon_trap();// 脱最適化を試行する。}

Page 23: Adaptive optimization of JIT compiler

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();// 脱最適化を試行する。}

Page 24: Adaptive optimization of JIT compiler

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 を引いて仮想関数を呼び出す}

Page 25: Adaptive optimization of JIT compiler

C2 の脱仮想化について

Mega­morphic call

呼び出し候補が 3 つ以上。呼び出し履歴が 3種以上。

// プロファイルの結果、呼び出し先に偏りがないP->invoke();//vtable を引いて仮想関数を呼び出す

Page 26: Adaptive optimization of JIT compiler

脱仮想化のベンチマーク結果

10割で呼ばれるメソッドと、

3種 3割で均等に呼ばれる仮想関数

呼び出しを 1000,000 回繰り返す。

10割で呼ばれるメソッド、 0.24秒

3割で均等に呼ばれる仮想関数 1.2秒

Corei7 で 3.4GHz なので、仮想関数呼び出しは 1 回あたり

3400clock 程度を消費している?

Page 27: Adaptive optimization of JIT compiler

適応的最適化と上手に付き合う?

JIT コンパイラは頑張ってくれてるけど、俺はどうすれば???

目的の関数が inline 展開されているか確認する。

処理のボトルネックに仮想関数呼び出しを使わない。 インスタンス化するのは一種類までに絞る。

JIT コンパイルされた最良なコードが実行されているかtrace する。

目的の関数が JIT コンパイルされているか確認する。

jdk1.5 とか結構はまる。

Oracle さんにサポートツール沢山あるんじゃないかな?

Page 28: Adaptive optimization of JIT compiler

オプションで確認する。

­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)

Page 29: Adaptive optimization of JIT compiler

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

Page 30: Adaptive optimization of JIT compiler

脱最適化ゾーン

共通のループ前準備

ガチガチチェックの1週しか回らないループ

真のカーネルループ

後処理ループ。カーネルループと変わらん。

OSR の後処理

Page 31: Adaptive optimization of JIT compiler

Hotspot の x86 向け最適化

JVM の低レイヤのお仕事。バイナリアン向け。

openjdk7/hotspot/src/cpu/x86/vm

MacroAssembler で書かれてる。 assembler_x86.cpp

専用の vmIntrinsics を用意して x86 向けに最適化

Prefetch の自動挿入

Cmpxchg で cas してみたり

文字列処理を自動で SSE42 使って高速化

Page 32: Adaptive optimization of JIT compiler

Hotspot の x86 向け最適化

invokevirtualString.equals(String)

MacroAssemblerchar_arrays_equals()

ptest命令使ってsimd

invokevirtualvmIntrinsics(string_equals)

callNode(StrEqualsNode)

bytecode

jvm の中

IdealNode

MachNode

Page 33: Adaptive optimization of JIT compiler

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

Page 34: Adaptive optimization of JIT compiler

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 キャッシュのみ

他のキャッシュには乗せない。

Page 35: Adaptive optimization of JIT compiler

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) ではない。

Page 36: Adaptive optimization of JIT compiler

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 の

いずれかなので、

出口は多少複雑。

Page 37: Adaptive optimization of JIT compiler

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); ...

Page 38: Adaptive optimization of JIT compiler

ベンチマーク結果

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

Page 39: Adaptive optimization of JIT compiler

おまけ (OpenJDK8)

OS対応

BSDへ対応

CPU 依存部分

AVXへ対応

高速な macro を追加 fast_pow(), fast_exp() FPInstruction を使って計算した log を元に pow と exp

C2 コンパイラ

OptimizePtrCompareオプションが追加。デフォルト true EscapeAnalysis が大幅改良。 PtrCompare でごにょ

nest した lock/unlock の除去。 MemBarNode が増えたり