Mais conteúdo relacionado
Semelhante a Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料) (20)
Mais de NTT DATA Technology & Innovation (20)
Javaコードが速く実⾏される秘密 - JITコンパイラ⼊⾨(JJUG CCC 2020 Fall講演資料)
- 1. © 2020 NTT DATA Corporation
Javaコードが速く実⾏される秘密
- JITコンパイラ⼊⾨
2020年11⽉7⽇ JJUG CCC 2020 Fall
株式会社NTTデータ 技術開発本部
阪⽥ 浩⼀
- 2. © 2020 NTT DATA Corporation 2
⾃⼰紹介
• 阪⽥ 浩⼀(さかた -)
• 通称 じゅくちょー
• JVMになりたい⼈
• NTTデータでJava/OpenJDK + GraalVMの
研究開発とそのサポート業に従事しています
• Javaチャンピオン
• OpenJDK Author
jyukutyo
- 3. © 2020 NTT DATA Corporation 3
このセッションの役割
JVMの現在の
実⾏速度を実現した
JITコンパイラの仕組みを
(概観として)伝える
- 4. © 2020 NTT DATA Corporation 4
Javaアプリケーションの
実⾏までの流れを
おさらいしましょう
- 5. © 2020 NTT DATA Corporation 5
javacでのコンパイル
Javaコード
コンパイル(javac)
クラスファイル
- 6. © 2020 NTT DATA Corporation 6
cafe babe 0000 0034 001d 0a00 0600 0f09 .......4........
0010 0011 0800 120a 0013 0014 0700 1507 ................
0016 0100 063c 696e 6974 3e01 0003 2829 .....<init>...()
5601 0004 436f 6465 0100 0f4c 696e 654e V...Code...LineN
756d 6265 7254 6162 6c65 0100 046d 6169 umberTable...mai
6e01 0016 285b 4c6a 6176 612f 6c61 6e67 n...([Ljava/lang
2f53 7472 696e 673b 2956 0100 0a53 6f75 /String;)V...Sou
7263 6546 696c 6501 000f 4865 6c6c 6f57 rceFile...HelloW
6f72 6c64 2e6a 6176 610c 0007 0008 0700 orld.java.......
クラスファイル
- 7. © 2020 NTT DATA Corporation 7
メソッドの処理を
Javaバイトコードで
記述する
- 8. © 2020 NTT DATA Corporation 8https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-7.html
- 9. © 2020 NTT DATA Corporation 9
Javaでのコンパイル(javac)
実施タイミング アプリケーション実⾏前
⼊⼒ Javaコード
出⼒ Javaバイトコード
- 10. © 2020 NTT DATA Corporation 10
$ javap -c HelloWorld
class HelloWorld {
...
public static void main(java.lang.String...);
Code:
0: getstatic #2
3: ldc #3
5: invokevirtual #4
8: return
}
javap
- 11. © 2020 NTT DATA Corporation 11
JVMは
クラスファイルを
読み込み、
アプリケーションを
実⾏する
- 12. © 2020 NTT DATA Corporation 12
JVMは
処理をどのように
実⾏する︖
- 13. © 2020 NTT DATA Corporation 13
バイトコードを
インタプリト、
つまり逐次処理する
- 14. © 2020 NTT DATA Corporation 14
今から25年前…
Javaの実⾏は
遅かった
- 15. © 2020 NTT DATA Corporation 15
理由の1つは、
すべての処理が
インタプリタ実⾏
だったこと
- 16. © 2020 NTT DATA Corporation 16
Java (J2SE) 1.3で
JITコンパイルを
標準で使うように
- 17. © 2020 NTT DATA Corporation 17
このJITコンパイルが
今でもJavaの速さの
主な要因
- 18. © 2020 NTT DATA Corporation 18
JITコンパイルの仕組み
Javaバイトコード
JITコンパイル
機械語
プロファイル情報
- 19. © 2020 NTT DATA Corporation 19
JITコンパイル
「実⾏中」に
機械語へコンパイル︕
JVM
- 20. © 2020 NTT DATA Corporation 20
各プラットフォーム
(主にCPU)
の機械語を
⽣成、キャッシュして
実⾏する
- 21. © 2020 NTT DATA Corporation 21
さらに、
実⾏時の情報を
プロファイリングし、
それを機械語⽣成に
活⽤する
- 22. © 2020 NTT DATA Corporation 22
その結果、
実⾏パフォーマンスが
向上する
- 23. © 2020 NTT DATA Corporation 23
コードをすべて
JITコンパイルしているのか︖
- 24. © 2020 NTT DATA Corporation 24
前提
JITコンパイルは
アプリケーションの実⾏中に
動作するものである
- 25. © 2020 NTT DATA Corporation 25
トレードオフ
JITコンパイル処理⾃体が
リソースを消費する
- 26. © 2020 NTT DATA Corporation 26
トレードオフのバランスを取る
プロファイル情報から
よく実⾏する処理
(頻繁に呼ばれる
メソッドなど)
をコンパイル対象にする
- 27. © 2020 NTT DATA Corporation 27
よく実⾏する処理
=
ホットスポット
- 28. © 2020 NTT DATA Corporation 28
(おそらく今使っている) JVM
HotSpot VM
(OpenJDK / Oracle JDK)
- 29. © 2020 NTT DATA Corporation 29
さらなるトレードオフ
よりよい機械語を
⽣成するには、
より⻑いコンパイル時間が
必要となる
- 30. © 2020 NTT DATA Corporation 30
さらなるトレードオフ
コンパイル時間短縮
or
速いコードの⽣成
- 31. © 2020 NTT DATA Corporation 31
HotSpot VMでは
2つのJITコンパイラを
使う
- 32. © 2020 NTT DATA Corporation 32
HotSpot VMでの2つのJITコンパイラ
• C1コンパイラ
• コンパイル時間が短い
• それほど速くない機械語する
• C2コンパイラ
• 速いコードを⽣成する
• コンパイル時間が⻑くなる
- 33. © 2020 NTT DATA Corporation 33
メソッドに対するコンパイルと呼び出し時間のイメージ
C2
C1
コンパイル時間が⻑くても、
その後の処理実⾏が⾼速になり、
「元が取れる」
- 34. © 2020 NTT DATA Corporation 34
Java歴が⻑い⼈向け
昔、実⾏オプションで
-server って
つけてましたよね︖
- 35. © 2020 NTT DATA Corporation 35
HotSpot VMでの2つのJITコンパイラ
• C1コンパイラ(clientコンパイラ)
• コンパイル時間が短い
• それほど速くない機械語する
• C2コンパイラ(serverコンパイラ)
• 速いコードを⽣成する
• コンパイル時間が⻑くなる
- 36. © 2020 NTT DATA Corporation 36
-server とは、
C2コンパイラを
使⽤する、
という意味です
- 37. © 2020 NTT DATA Corporation 37
Java 8からは-server不要
Tiered Compilation
(階層型コンパイル)
がデフォルトに
なったため
- 38. © 2020 NTT DATA Corporation 38
バイトコード C1⽣成機械語 C2⽣成機械語
Tiered Compilation概要
• C1とC2の両⽅を使う
- 39. © 2020 NTT DATA Corporation 39
JITコンパイルのログを
出⼒してみよう︕
- 40. © 2020 NTT DATA Corporation 40
JITコンパイルのログ設定
• 以下のオプションを付ける
• -XX:+PrintCompilation
• コンソールにコンパイルログを出⼒する
- 41. © 2020 NTT DATA Corporation 41
PrintCompilationの出⼒
181 416 4 java.lang.AbstractStringBuilder::append (45 bytes)
182 417 ! 3 java.util.zip.InflaterInputStream::read (138 bytes)
182 420 n 0 java.util.zip.Inflater::inflateBytesBytes (native)
182 418 3 java.util.zip.InflaterInputStream::ensureOpen (18 bytes)
182 419 3 java.io.RandomAccessFile::seek (22 bytes)
183 421 3 java.util.WeakHashMap::getTable (9 bytes)
183 422 ! 3 java.util.WeakHashMap::expungeStaleEntries (139 bytes)
184 423 3 java.util.WeakHashMap::indexFor (6 bytes)
184 424 3 java.io.File::isInvalid (48 bytes)
184 427 3 java.util.zip.ZipUtils::CENSIZ (9 bytes)
184 428 3 java.io.UnixFileSystem::normalize (41 bytes)
185 429 3 java.util.zip.ZipFile::ensureOpen (40 bytes)
185 425 1 java.nio.HeapByteBuffer::isDirect (2 bytes)
185 431 s 3 jdk.internal.loader.Resource::cachedInputStream (20 bytes)
- 42. © 2020 NTT DATA Corporation 42
デモ
SpringBoot起動での
JITコンパイルログ出⼒
- 43. © 2020 NTT DATA Corporation 43
実際のJITコンパイルログ
32 5 3 java.lang.String::coder (15 bytes)
...
150 235 4 java.lang.String::coder (15 bytes)
151 5 3 java.lang.String::coder (15 bytes) made not entrant
経過時間
コンパイルID
最適化レベル 破棄した
コンパイルした
メソッドのバイト数
- 44. © 2020 NTT DATA Corporation 44
JITコンパイルの最適化レベル
最適化レベル: 0
インタプリタ
⼀般的ケース
プロファイリング
なし
ベーシック
カウンタのみ
詳細
3 4
C2が詰まっているケース 0 2 43
効果が薄いメソッドの
ケース
0 1 3
C1コンパイラ C2
- 45. © 2020 NTT DATA Corporation 45
JITコンパイラの最適化レベル
0 インタプリタ実⾏
1 C1 (プロファイリングなし)
2
C1 (ベーシックカウンタのみ
プロファイリング)
3 C1 (完全なプロファイリング)
4 C2
- 46. © 2020 NTT DATA Corporation 46
$ javap -c --module java.base java.lang.String
...
byte coder();
Code:
0: getstatic
3: ifeq
6: aload_0
7: getfield
10: goto
13: iconst_1
14: ireturn
メソッドのバイト数
String#coder()は
15バイトのメソッド︕
- 47. © 2020 NTT DATA Corporation 47
再掲︓実際のJITコンパイルログ
32 5 3 java.lang.String::coder (15 bytes)
...
150 235 4 java.lang.String::coder (15 bytes)
151 5 3 java.lang.String::coder (15 bytes) made not entrant
経過時間
コンパイルID
最適化レベル 破棄した
コンパイルした
メソッドのバイト数
- 48. © 2020 NTT DATA Corporation 48
JITコンパイルのログ設定
• 以下のオプションを付ける
• -XX:+PrintCompilation
• コンソールにコンパイルログを出⼒する
• Unified JVM Loggingでも出⼒できます
• -Xlog:jit+compilation=debug
- 49. © 2020 NTT DATA Corporation 49
JITコンパイラが
コンパイルしたメソッドは
わかったけど
⽣成した機械語も⾒たい︕
- 50. © 2020 NTT DATA Corporation 50
たとえばこういったケース
• あるメソッドがJITコンパイルされているか
確認したい
• パフォーマンスに⼤きく影響するメソッドである場合
• 実⾏中のどの時点でJITコンパイルされたかが重要な場合
• オプションの変更による影響を確認する場合
- 51. © 2020 NTT DATA Corporation 51
HSDIS(HotSpot Disassembler)
• JIT⽣成の機械語をアセンブリに変換して
出⼒するライブラリ
• HotSpot VM⽤(通常のOpenJDKとOracle JDK)
• OpenJDKに含まれている
• src/utils/hsdis ディレクトリで⾃分でビルドする
• ビルド⼿順参考︓https://www.slideshare.net/nttdata-tech/java-maximize-cpu-2019-nttdata-suenaga
• HSDISをビルドし、JDKに配置する
• [JAVA_HOME]/lib ディレクトリ
• 以下のオプションを付けて起動する
• -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
- 52. © 2020 NTT DATA Corporation 52
HSDISの出⼒
Demo2.workload(I)I (Demo2.workload(int)) [0x000000010b997b20, 0x000000010b997b60] 64 bytes
[Entry Point]
[Constants]
# {method} {0x000000012709faf8} 'workload' '(I)I' in 'Demo2'
# this: rsi:rsi = 'Demo2'
# parm0: rdx = int
# [sp+0x10] (sp of caller)
0x000000010b997b20: mov 0x8(%rsi),%r10d
0x000000010b997b24: shl $0x3,%r10
0x000000010b997b28: cmp %r10,%rax
0x000000010b997b2b: jne 0x000000010b92f000 ; {runtime_call ic_miss_stub}
0x000000010b997b31: nop
0x000000010b997b32: nopl 0x0(%rax)
0x000000010b997b39: nopl 0x0(%rax)
[Verified Entry Point]
0x000000010b997b40: nopl 0x0(%rax,%rax,1)
0x000000010b997b45: inc %edx ;*iadd {reexecute=0 rethrow=0 return_oop=0}
; - Demo2::workload@2 (line 9)
0x000000010b997b47: mov %edx,%eax ;*ireturn {reexecute=0 rethrow=0 return_oop=0}
- 53. © 2020 NTT DATA Corporation 53
デモ
SpringBoot起動時の
アセンブリ表⽰
- 55. © 2020 NTT DATA Corporation 55
出⼒するメソッドを指定できる
• CompileOnlyオプションを付与する
• -XX:CompileOnly=String::startsWith -XX:CompileCommand=quiet
• コンパイラディレクティブを使う
1. JSONファイルで対象を指定する
2. -XX:+UnlockDiagnosticVMOptions -XX:CompilerDirectivesFile=directive.json
[
{
match: "java/lang/String.*",
PrintAssembly: true
}
]
- 56. © 2020 NTT DATA Corporation 56
参考︓JITWatch
• JITコンパイルログの解析と可視化ができる
• https://github.com/AdoptOpenJDK/jitwatch
- 57. © 2020 NTT DATA Corporation 57
Javaバイトコードを
機械語にする
プロセスは︖
- 58. © 2020 NTT DATA Corporation 58
JITコンパイルのプロセス
Java
バイトコード
IR
(中間表現)
機械語
最適化を適⽤し、
IRを変更するサイクル
を繰り返す
① ③
②
- 59. © 2020 NTT DATA Corporation 59
IR(Intermediate Representation)
• コードをデータ構造で表現する
• JVMでは、グラフで表現している
• プログラムの依存をグラフにする
• 通称 IRグラフ
- 60. © 2020 NTT DATA Corporation 60
例: x + y をグラフにする
- 61. © 2020 NTT DATA Corporation 61
例: getX() + getY() をグラフにする
- 62. © 2020 NTT DATA Corporation 62
例: メソッドの呼び出し順序を考慮
⻩⾊が実⾏順序、緑⾊がデータフロー
- 63. © 2020 NTT DATA Corporation 63
JVMのIRグラフを
⾒てみよう
(ただし、C2ではなく
GraalVM JITコンパイラ)
- 64. © 2020 NTT DATA Corporation 64
IdealGraphVisualizer
IRグラフ可視化ツール
• ただし、GraalVM JITコンパイラ⽤
• -Dgraal.Dump=:1 オプションをつけて実⾏する
• https://www.graalvm.org/docs/reference-manual/tools/#ideal-graph-visualizer
- 65. © 2020 NTT DATA Corporation 65
参考: C2とGraalVM JITコンパイラ
• C2
• OpenJDKのデフォルト
• C++で書かれている
• GraalVM JITコンパイラ
• GraalVMのデフォルト
• Javaで書かれている
• OpenJDKにもポートされている (10以降)
• JEP 317: Experimental Java-Based JIT Compiler
- 66. © 2020 NTT DATA Corporation 66
デモ
IRグラフをダンプして
IGVで表⽰する
- 67. © 2020 NTT DATA Corporation 67
JITコンパイルでの
最適化とは、
このグラフに対する
パターンマッチとなる
イメージ
- 68. © 2020 NTT DATA Corporation 68
ものすごくラフな例
Aノード Bノード
Cノード
Dノード Dノード
Eノード
最適化パターンにマッチ︕
- 69. © 2020 NTT DATA Corporation 69
JITコンパイラの構成
バイト
コード
フロント
エンド
バック
エンド
機械語
- 70. © 2020 NTT DATA Corporation 70
フロントエンド
• ハードウェアから独⽴
• バイトコードからIRを⽣成する
• IRに最適化をほどこす
• IRは、HIRと呼ぶ
• High-Level IR: ⾼⽔準中間表現
- 71. © 2020 NTT DATA Corporation 71
バックエンド
• ハードウェアに依存
• レジスタを割り当てる
• IRから機械語を⽣成する
• IRはLIRと呼ぶ
• Low-Level IR: 低⽔準中間⾔語
- 72. © 2020 NTT DATA Corporation 72
JITコンパイルの処理の流れ
クラス
ロード
バイト
コード
HIR
HIR LIR
LIR
機械語
コード
フロントエンド バックエンド
HIR⽣成
最適化 レジスタ割当
コード⽣成
- 73. © 2020 NTT DATA Corporation 73
o.g.compiler.core.GraalCompiler
public static <T extends CompilationResult> T
compile(Request<T> r) {
...
emitFrontEnd(r.providers, r.backend, r.graph,
r.graphBuilderSuite, r.optimisticOpts,
r.profilingInfo, r.suites);
r.backend.emitBackEnd(r.graph, null,
r.installedCodeOwner, r.compilationResult,
r.factory, null, r.lirSuites);
...
}
- 74. © 2020 NTT DATA Corporation 74
最適化
• メソッドのインライン化
• ループアンロール
• デッドコード削除
• ロック粗粒化 / ロック省略
• エスケープ解析 (厳密には最適化ではない)
• などなど多数
- 75. © 2020 NTT DATA Corporation 75
ホットパスでのコンパイル
ノード ノード
ノード
ノード
プロファイル結果から、
左ルートを前提とした
コードを⽣成する
- 76. © 2020 NTT DATA Corporation 76
実⾏が右ルートに⾏く場合は︖
ノード ノード
ノード
ノード
⽣成したコードにある
トラップに⼊る
- 77. © 2020 NTT DATA Corporation 77
脱最適化: Deoptimization
• JITコンパイラが⽴てた前提から
外れた実⾏となった場合
• if – else if でまれな経路に⼊る
• クラスロードでインタフェースの実装クラスが増える
• 機械語の実⾏から
インタプリタでの実⾏に戻る
- 78. © 2020 NTT DATA Corporation 78
JITコンパイルのライフサイクル
インタプリタ
コードキャッシュ
C1
C2
脱最適化
- 79. © 2020 NTT DATA Corporation 79
コードキャッシュ
• 機械語コードは
CodeCache (C++オブジェクト)内に
配置する
• -XX:ReservedCodeCacheSize でキャッシュサイズを指定
• デフォルトサイズは、240MB
- 80. © 2020 NTT DATA Corporation 80
Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full.
Compiler has been disabled.
Java HotSpot(TM) 64-Bit Server VM warning: Try increasing the
code cache size using -XX:ReservedCodeCacheSize=
CodeCache: size=2496Kb used=1980Kb max_used=1983Kb free=515Kb
bounds [0x0000000103db8000, 0x0000000104028000,
0x0000000104028000]
total_blobs=1104 nmethods=623 adapters=288
compilation: disabled (not enough contiguous free space left)
コードキャッシュあふれ
- 81. © 2020 NTT DATA Corporation 81
アプリケーションの
パフォーマンスが
低下する
- 82. © 2020 NTT DATA Corporation 82
今回扱っていない話題
• コンパイルキュー
• OSR: On Stack Replacement
• 各最適化⼿法の詳細
• SSA: Static Single Assignment
• レジスタ割付のアルゴリズム
• Linear Scan Register Allocation
- 83. © 2020 NTT DATA Corporation 83
まとめ
• JITコンパイラ
• バイトコードを機械語に変換する
• HotSpot VMのJITコンパイラ
• C1とC2でTiered Compilation
• HSDIS(+JITWatch)
• JIT⽣成コードを実際に⾒れる
- 84. © 2020 NTT DATA Corporation本資料に記載されている会社名、商品名、⼜はサービス名は、各社の登録商標⼜は商標です