Upload
jyukutyo
View
3.609
Download
1
Embed Size (px)
DESCRIPTION
2014/10/27 関ジャバ HotSpot meetingで@jyukutyoが発表した「JITコンパイルはじめの一歩」の資料です。
Citation preview
@jyukutyo(じゅくちょ) 阪田 浩一
JITコンパイル はじめの一歩
1
自己紹介
3
@jyukutyo
著書 3冊
関西Javaエンジニアの会 (関ジャバ) 発起人・運営
Fight the Future http://jyukutyo.hatenablog.com/
フリュー株式会社 所属 プリ機と連携する
画像SNS「ピクトリンク」 開発・運用に従事. 会員数800万人!
ScalaMatsuri スポンサー企業!
エンジニア歴11年 35歳SI業界での客先常駐 9年 Web系? 2年強
文学部哲学科卒
塾講師アルバイト
阪田 浩一
JVM力(ちから)は 今日の発表者の 中で最弱…
よろしくお願いします
まずは おさらい
JITコンパイルって何?
厳密な定義では
JITベースの仮想マシンは 実行前に
全バイトコードを マシンコードに 変換する(!)
ここでは JITコンパイルを 実行時に
動的コンパイルする という意味で進めます
Just-In-Timeコンパイル !
メソッドが呼び出されたときに メソッドのバイトコードをコンパイルして ネイティブなマシンコードに変換する
メリット
コンパイル済みの コードを呼び出すので、
実行が速い
デメリット
メソッドをコンパイルする 処理に
大きなオーバーヘッドが ある
このコンパイルの タイミングで
最適化も実施する
JVMでは実行時に プロファイリングし、
コンパイルに値する コードを判断する
最適化の種類
§ メソッド呼び出しのインライン化 § 単一ディスパッチ § 共通部分式の削除 § ループ・アンローリング § 範囲チェック削除 § デッドコードの削除 § データ・フロー解析
例) メソッドのインライン化
呼び出している メソッドの本体を 呼び出し元に コピーする
イメージしやすい
これによって 何がよいのか??
小さな処理の場合、 メソッドを呼び出す コストの方が 高くなる
デフォルトでは 35バイト未満の バイトコードを含む メソッドが対象となる ※静的なルール
ただ、動的なポリシーも あるので、 対象は
アプリケーションごとに 変化する
例) 単一ディスパッチ
ポリモーフィックな メソッド呼び出しにおいて、 参照する型が変わらない
と想定し、
仮想メソッド検索を スルーする
(オーバーライドが 使用されるかを チェックしない)
脱最適化
単一ディスパッチの想定と 違う型のインスタンス であった場合など
コンパイルしたコードを 無効にして
インタプリタに戻る (または再コンパイルする)
つまり
1つのメソッドに対して 脱最適化と 再コンパイルが 繰り返されることも よくあること
コンパイル・モード
一度は目にした -client -server オプション
-client:C1 コンパイル処理は速い
生成されるコードは比較的遅い
-server:C2 コンパイル処理は遅い 生成されるコードは速い
-client:C1§ ステップ1(インタプリタ)
§ 実行しながらホットなメソッドを検出する
§ ステップ2(ネイティブ)
§ ホットと判断したメソッドをJITコンパイルし、実行する
-server:C2§ ステップ1(インタプリタ)
§ 実行しながらプロファイリングする
§ ステップ2(ネイティブ)
§ ホットと判断したメソッドをステップ1で取得したプロファイリングデータを使ってJITコンパイルし、実行する
§プロファイリングデータを取得するためにインタプリタのフェーズが長い
JVMエンジニア たるもの
これを使い分けねば ならん!
そんな時代も ありました…
もうこんな オプション 使いません
今は 自動的に
両方とも使っています
階層型コンパイル JDK7で導入(オプション※で指定が必要)
JDK6u25までバックポート JDK8ではデフォルトで有効
!※-XX:+TieredCompilation
端的に言うと、
起動時には C1を多く使い、 速く起動する
正常に動作するように なればC2をメインに使い、 パフォーマンスを高める
もう少し 詳しく見ると、
インタプリタではなく C1の段階で
プロファイリングする ようにする
コンパイルレベル§ level 0:インタプリタ
§ level 1:C1 フル最適化 § プロファイリングなし
§ level 2:C1 呼び出しとループのプロファイリング
§ level 3:C1 フルプロファイリング
§ level 4:C2
コンパイルの 処理方法
C1,C2がそれぞれ キューを持つ
C1,C2がそれぞれ キューを持つ
キューの長さに応じて 閾値などを 調整する
コードキャッシュ内に コンパイル後の
コードが配置されると、
インタプリタ・モードから コンパイルされたコードを 使用するモードに 切り替える
これは、ポインタ書き換え (pointer swizzing) と呼ばれる こともある
コンパイルレベルの遷移§ level 0 -> 3 -> 4(理想的)
§ 0 -> 2 -> 3 -> 4(C2のキューが長いとき)
§ 0 -> 3 -> 1(C2コンパイルができないメソッドのとき)??
§ 0 -> 4(C1でコンパイルできない、インタプリタでプロファイリングしてC2する)
連続再コンパイル
一度コンパイルしたあとも プロファイリングを
続ける
さらなる最適化の 可能性があれば、 コードを再コンパイル
する
On-Stack replacement (OSR)
ループの中で 同一のメソッドを 呼び出している ような場合
ループの最中に インタプリタから
コンパイルしたコードを 使うように 切り替える
JITコンパイルの 確認方法
-XX:+PrintCompilation ! 75 1 3 java.lang.Math::min (11 bytes) 75 3 3 java.lang.String::charAt (29 bytes) 76 2 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (16 bytes) 76 4 n 0 java.lang.System::arraycopy (native) (static) 77 5 3 java.lang.Object::<init> (1 bytes) 77 6 3 java.lang.String::hashCode (55 bytes) 77 7 3 java.lang.String::indexOf (70 bytes) 78 8 3 java.lang.String::length (6 bytes) 78 9 3 java.lang.AbstractStringBuilder::append (50 bytes) 78 10 3 java.lang.String::getChars (62 bytes) 78 11 3 java.lang.String::equals (81 bytes)
コンパイルされた メソッドと
時間、コンパイルレベルが わかる
-XX:+PrintTieredEvents !0.047899: [call level=0 [java.lang.Object.<init>()V] @-1 queues=0,0 rate=n/a k=1.00,1.00 total=128,0 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.048180: [loop level=0 [sun.nio.cs.UTF_8$Decoder.decode([BII[C)I] @20 queues=0,0 rate=n/a k=1.00,1.00 total=29,1024 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.048348: [loop level=0 [sun.nio.cs.UTF_8$Decoder.decode([BII[C)I] @20 queues=0,0 rate=n/a k=1.00,1.00 total=45,2048 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.048414: [loop level=0 [java.lang.String.hashCode()I] @24 queues=0,0 rate=n/a k=1.00,1.00 total=73,1024 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.049419: [call level=0 [java.lang.String.hashCode()I] @-1 queues=0,0 rate=n/a k=1.00,1.00 total=128,1302 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.049514: [call level=0 [java.lang.Object.<init>()V] @-1 queues=0,0 rate=n/a k=1.00,1.00 total=256,0 mdo=0(0),0(0) max levels=0,0 compilable=c1,c1-osr,c2,c2-osr status=idle]0.049557: [compile level=3 [java.lang.Object.<init>()V] @-1 queues=0,0 rate=n/a k=1.00,1.00]!
より詳細に 見れる
さらに詳細に 見るには
-XX:+LogCompilation !
XMLフォーマットで出る -XX:+UnlockDiagnosticVMOptions
を同時に設定する必要あり !
hotspot_pid9999.log !<nmethod compile_id='2' compiler='C2' entry='0x000000010508d220' size='1504' address='0x000000010508d0d0' relocation_offset='296' insts_offset='336' stub_offset='688' scopes_data_offset='736' scopes_pcs_offset='864' dependencies_offset='1456' handler_table_offset='1464' nul_chk_table_offset='1488' method='java/lang/String indexOf (II)I' bytes='70' count='366' backedge_count='6418' iicount='366' stamp='0.122'/>!
もう読めません…
ツールを使おう!
セットアップは cloneして mvn package 用意されている launchUI.sh(.bat) を実行するだけ
超簡単!
ログを解析するときは
-XX:+TraceClassLoadingでクラスロード時の 情報も出す
さらに
HotSpotの 内部オプション
-XX:+PrintAssembly をつければ
アセンブリのコードを 見れる
※HotSpot disassembleer (HSDIS) が別途必要
https://kenai.com/projects/base-hsdis JDK 8ならファイルをJAVA_HOME/jre/lib/server/へ
コピーする
デモ
ご清聴 ありがとう ございました!