© 2019 NTT DATA Corporation 1 © 2019 NTT DATA Corporation
NTTデータ テクノロジーカンファレンス 2019
JavaでCPUを使い倒す!
~Java 9以降のCPU最適化を覗いてみる~
2019年9月5日
株式会社NTTデータ 技術開発本部 先進基盤技術グループ 末永 恭正
#NTTDATATC
© 2019 NTT DATA Corporation 2 #NTTDATATC
“プログラムを動かす”ということ
© 2019 NTT DATA Corporation 3 #NTTDATATC
プログラムの表現方法は1つではない
There’s more than one way to do it
• Perlのモットー
• Perlに限らず、どのプログラミング言語にも言えること
public void sayHelloWorld(){
System.out.println(“Hello World”);
}
public void sayHelloWorld(){
var out = new FileOutputStream(FileDescriptor.out);
try{
out.write(“Hello World¥n”.getBytes());
}
catch(IOException e){
e.printStackTrace();
}
}
© 2019 NTT DATA Corporation 4 #NTTDATATC
プログラムをCPUで動かす方法も1つではない
処理はCPUが行う
同じプログラムでもCPUの動かし方は何通りも存在する
public void addArray(int[] a, int[] b, int[] result){
for(int i = 0; i < 8; i++){
result[i] = a[i] + b[i];
}
}
mov $7, %rcx # Loop index ([7] to [0])
1:
mov (%rax, %rcx, 4), %esi # Load a[i]
add (%rbx, %rcx, 4), %esi # a[i] + b[i]
mov %esi, (%rdi, %rcx, 4) # Store result
sub $1, %rcx # Decrement index
jns 1b
vmovdqa (%rax), %ymm1 # Load a[]
vpaddd (%rbx), %ymm1, %ymm2 # a[] + b[]
vmovdqa %ymm2, (%rdi) # Store result
© 2019 NTT DATA Corporation 5 #NTTDATATC
CPUに”いい感じに”仕事してもらう
• 効率のいいロジックを書く
• 効率のいいAPI、ライブラリなどを利用する
• コンパイラ最適化
• 様々な最適化手法
• 特定CPUにターゲティングした最適化
• プロファイリング
• 動作状況把握(分岐、引数、コールパス、etc…)
• JVMは実行時プロファイルに基づいて機械語を生成
• Just-In-Time: JIT
© 2019 NTT DATA Corporation 6 #NTTDATATC
Javaプログラムのコンパイル
javac=バイトコード生成(≠機械語生成)
• JVMが解釈可能なバイトコード「のみ」を生成
• JVMなしでは(OSが直接)実行できない
• バイトコード最適化は意味がない可能性あり
• JITによる最適化で効果が消える or 悪化する可能性あり
• 一部処理(文字列結合など)はjavacが効率よいものへ
自動変換
© 2019 NTT DATA Corporation 7 #NTTDATATC
バイトコードの実行
HotSpot(OpenJDK / Oracle JDKのJVM)の場合
1. インタープリタ
– バイトコードを逐次実行
– 実行状態をプロファイルして蓄積
2. JITコンパイル
– 複数の最適化レベルに分けて機械語に翻訳(Tier 1~4)
– メソッド全体、または一部を機械語へマルっと翻訳
– 処理によっては決まった機械語へマルっと置き換え
本日のテーマ
© 2019 NTT DATA Corporation 8 #NTTDATATC
JavaとCPU
© 2019 NTT DATA Corporation 9 #NTTDATATC
OpenJDKとチップベンダ
様々なチップベンダがOpenJDKコミュニティへパッチを寄贈
• Intel
• AMD
• Arm
• Qualcomm
• Huawei
© 2019 NTT DATA Corporation 10 #NTTDATATC
JavaとIntel製CPUのリリース時期
2014 2015 2016 2017 2018 2019
JDK 8
2014/03
JDK 9
2017/09
JDK 10
2018/03
JDK 11
2018/09
JDK 12
2019/03
JDK 13
2019/09
Broadwell
2014 Q3
Skylake
2015 Q3
Kaby Lake
2017 Q1
Coffee Lake
2017 Q4
Whiskey Lake
2018 Q3
Cascade Lake
2019 Q2
VNNIAVX-512
Ice Lake
2019 Q3
© 2019 NTT DATA Corporation 11 #NTTDATATC
JDK 8までのx86(拡張命令含む)への対応状況
• 暗号処理(AES-NI)
• 同期処理(TSX)
• SIMD
• SSEやAVXを活用したJIT生成コードのベクタライズ
• 文字列処理など、一部APIの処理置換
• Intelの最適化マニュアルに基づく改善
© 2019 NTT DATA Corporation 12 #NTTDATATC
OpenJDK 9
2017/09 – 2018/03
<< EOL’ed >>
© 2019 NTT DATA Corporation 13 #NTTDATATC
JITのコード生成に関する主な改善内容
• SIMD命令サポート
• JDK-8081247: AVX 512 extended support
• JDK-8144771: Use AVX3 instructions for string compare
• JDK-8154975: Update for vectorizedMismatch with AVX512
• JDK-8139340: SuperWord enhancement to support vector conditional move (CMovVD ) on Intel AVX cpu
• 数学ライブラリの改善
• JDK-8135028: support for vectorizing double precision sqrt
• JDK-8132207: update for x86 exp in the math lib
• JDK-8139575: update for x86 log in the math lib
• JDK-8145688: update for x86 pow in the math lib
• JDK-8143353: update for x86 sin and cos in the math lib
• JDK-8154122: Intrinsify fused mac operations on x86
• セキュリティ系ライブラリの改善
• JDK-8134553: CRC32C implementations for x86/x64 targets
• JDK-8143925: enhancing CounterMode.crypt() for AESCrypt.implEncryptBlock()
• JDK-8150767: Enables SHA Extensions on x86
• JDK-8152354: Update for x86 AES CBC Decryption
• JDK-8154495: Update for x86 SHA256 using AVX2
• JDK-8165381: Update for x86 SHA512 using AVX2
© 2019 NTT DATA Corporation 14 #NTTDATATC
AVX 512への対応
• 512ビットレジスタを使ったIntelのSIMD命令
• 2016年からXeon Phiに導入
• 一部のハイエンド向けプロセッサへも2017年から導入
• AVX512BWやAVX512DQなど複数の仕様が存在
• プロセッサによって使える命令が異なる場合あり
• Core i5など「一般向け」への導入は2019年から
© 2019 NTT DATA Corporation 15 #NTTDATATC
AVX 512:ループ処理のベクタライズ
:
vpaddd 0x50(%r8,%rbx,4),%ymm0,%ymm0
:
vpaddd 0x70(%r8,%rbx,4),%ymm0,%ymm0
:
:
vpaddd 0x90(%r8,%rbp,4),%zmm0,%zmm0
:
vpaddd 0xd0(%r8,%rbp,4),%zmm0,%zmm0
:
public void addArray(int[] a, int[] b, int[] result){
for(int i = 0; i < a.length; i++){
result[i] = a[i] + b[i];
}
}
• ZMMレジスタを使った64バイトずつの処理
• ループ処理がより効率的に!
0x70-0x50=0x20(32バイト) 0xd0-0x90=0x40(64バイト)
AVX 2 AVX 512
© 2019 NTT DATA Corporation 16 #NTTDATATC
AVX 512:文字列処理
longString.compareTo(anotherLongString);
• SIMDを使った最適化でよく出てくるネタの1つ
• JavaでもcompareTo()やindexOf()等のString操作は
よく最適化される
ランダムな文字列の比較で1.22倍の性能向上
© 2019 NTT DATA Corporation 17 #NTTDATATC
FMAへの対応
• Fused Multiply-Addの略
• 積和演算
• MAC(Multiply Accumulate)とも呼ばれる
• 信号処理でよく出てくるパターン
• C言語でもC99からfma()が導入
• 計算が速いだけでなく、浮動小数の丸め誤差の影響縮小
• Java 9からMath::fmaもAPIの仲間入り
このメソッド呼び出しを最適化
© 2019 NTT DATA Corporation 18 #NTTDATATC
例:FIRフィルタ
𝑦𝑦[𝑛𝑛] = �
𝑖𝑖=0
𝑁𝑁−1
𝐴𝐴 𝑖𝑖 𝐵𝐵[𝑛𝑛 − 𝑖𝑖]
public double calcFIR(double[] a, double[] b){
int N = a.length;
int n = N – 1;
double result = 0.0d;
for(int i = 0; i < N; i++) {
result += a[i] * b[n – i];
}
return result;
}
public double calcFIR(double[] a, double[] b){
int N = a.length;
int n = N – 1;
double result = 0.0d;
for(int i = 0; i < N; i++) {
result = Math.fma(a[i], b[n – i], result);
}
return result;
}
普通に計算 FMA利用
© 2019 NTT DATA Corporation 19 #NTTDATATC
例:FIRフィルタ
result += a[i] * b[n – i]; result = Math.fma(a[i], b[n – i], result);
vmulsd %xmm2,%xmm1,%xmm1
vaddsd %xmm0,%xmm1,%xmm1
vfmadd231sd %xmm2,%xmm1,%xmm0
JITコンパイル JITコンパイル
250112.3323524159 250112.33235242742
計算結果 計算結果
• 普通に計算した場合でもベクタライズされる
• 普通に計算:2命令、FMA:1命令
• 計算結果も若干違う!
普通に計算 FMA利用
N=1,000,000、Randomで生成した配列の計算例
© 2019 NTT DATA Corporation 20 #NTTDATATC
SHA計算
• Secure Hash Algorithm
• TLS通信などでおなじみ
• ハッシュサイズに応じて仕様が複数存在
• プロセッサのSHA-NIサポートが必須
• ただし、SHA-256とSHA-512はAVX 2のみで最適化実施
• プログラムを変更しなくても性能が稼げる!
• JDK付属のセキュリティプロバイダ(SUN)の利用が条件
MessageDigest.getInstance(“SHA-512”);
© 2019 NTT DATA Corporation 21 #NTTDATATC
OpenJDK 10
2018/03 – 2018/09
<< EOL’ed >>
© 2019 NTT DATA Corporation 22 #NTTDATATC
JITのコード生成に関する主な改善内容
• SIMD命令サポート
• JDK-8178811: Minimize the AVX <-> SSE transition penalty through generation of vzeroupper instruction on x86
• JDK-8192846: Support cmov vectorization for float
• 数学ライブラリの改善
• JDK-8181616: FMA Vectorization on x86
• JDK-8190800: Support vectorization of Math.sqrt() on floats
© 2019 NTT DATA Corporation 23 #NTTDATATC
ペナルティの改善
Intelの最適化マニュアルより
意訳: AVX命令とSSE命令が連続して実行されるときはVZEROUPPERしようね!
© 2019 NTT DATA Corporation 24 #NTTDATATC
ペナルティの改善
ココをゼロクリア
XMM (128bit)
YMM (256bit)
2551280
• SSEレジスタとAVXレジスタは一部を共有
• レガシーSSE(VEXプリフィックスなし)
実行でCPUステートに関するペナルティ発生
• 上位128bitをクリアすることで
ペナルティを回避
※CPU世代によって異なります
127
SPECjvm 2008で3%の性能改善
© 2019 NTT DATA Corporation 25 #NTTDATATC
OpenJDK 11
2018/09 –
<< Long Term Support (LTS) >>
© 2019 NTT DATA Corporation 26 #NTTDATATC
JITのコード生成に関する主な改善内容
• SIMD命令サポート
• JDK-8199421: Add support for vector popcount
• JDK-8201193: Use XMM/YMM for objects initialization
• セキュリティ系ライブラリの改善
• JDK-8205398: AES CBC decryption algorithm using AVX512 instructions
© 2019 NTT DATA Corporation 27 #NTTDATATC
オブジェクト初期化
• HotSpotはJavaオブジェクトの初期化にERMS
(Enhanced REP MOVSB)を利用
• 最適化ガイドにも書いてある、妥当な方法
• glibcのmemcpy実装などでも使われている
• ERMSをサポートしないプロセッサ向けにSSE/AVX命令
を使ったオブジェクト初期化が実装された
• コントリビューターはAMD EPYC向けを意図
• Ivy Bridge以降のIntelプロセッサは影響なし
© 2019 NTT DATA Corporation 28 #NTTDATATC
将来導入予定の命令への対応
• Intelの将来的な命令セット拡張リファレンスで
Ice Lakeから導入が予定されている命令への対応
• VAES :AES CBC暗号の復号高速化
• VPOPCNTDQ :Integer::bitCountの高速化
© 2019 NTT DATA Corporation 29 #NTTDATATC
OpenJDK 12
2019/03 – 2019/09
© 2019 NTT DATA Corporation 30 #NTTDATATC
JITのコード生成に関する主な改善内容
• SIMD命令サポート
• JDK-8210764: Update avx512 implementation
• セキュリティ系ライブラリの改善
• JDK-8214074: Optimize Ghash using AVX instructions
• その他、CPUを意識した修正
• JDK-8213479: Missing x86_64.ad patterns for 8-bit logical operators with destination in memory
• JDK-8214751: X86: Support for VNNI Instructions
© 2019 NTT DATA Corporation 31 #NTTDATATC
VNNI
• Vector Neural Network Instructions
• Intel DL Boost
• AVX 512の拡張
• ディープラーニングの推論性能を向上させる拡張命令
• Cascade Lake以降のプロセッサで利用可能
© 2019 NTT DATA Corporation 32 #NTTDATATC
VNNI
public void mulAdd(short[] in1, short[] in2, int[] out){
for(int i = 0; i < N; i++){
out[i] += ((in1[2*i] * in2[2*i]) + (in1[2*i+1] * in2[2*i+1]));
}
}
vpmaddwd %ymm0,%ymm1,%ymm0
vpaddd 0x10(%r13,%rbx,4),%ymm0,%ymm0
vpdpwssd %zmm1,%zmm2,%zmm0
• ZMMレジスタを使った64バイトずつの処理
• 掛けて足して(vpmaddwd)また足す(vpaddd)が
VNNIが提供する1命令(vpdpwssd)に
Whiskey Lake Cascade Lake
© 2019 NTT DATA Corporation 33 #NTTDATATC
OpenJDK 13
2019/09 – 2020/03
© 2019 NTT DATA Corporation 34 #NTTDATATC
JITのコード生成に関する主な改善内容
• SIMD命令サポート
• JDK-8215483: Off heap memory accesses should be vectorized
• 数学ライブラリの改善
• JDK-8217561: X86: Add floating-point Math.min/max intrinsics
• その他、CPUを意識した修正
• JDK-8216580: Fix generation of VNNI vector code by allowing adjacent LoadS nodes to be isomorphic
• JDK-8222074: Enhance auto vectorization for x86
• JDK-8217909: Make unused r12 register (without compressed oops) available to regalloc in C2
© 2019 NTT DATA Corporation 35 #NTTDATATC
レジスタの有効活用
• 圧縮Oopのヒープ情報保持目的に使っていたレジスタを
無効時に自由に使えるようにした修正
• -XX:UseCompressedOops
• 32GB以下のヒープサイズで動作するポインタ圧縮機能
• 使えるレジスタが増える=ロード/ストアの効率化
• JDK 13のリリースノートにも掲載予定
© 2019 NTT DATA Corporation 36 #NTTDATATC
レジスタの有効活用
movzbl 0x18(%r9),%r8d
;*iand {reexecute=0 rethrow=0 return_oop=0}
; - java.lang.StringLatin1::hashCode@31 (line 196)
; - java.lang.String::hashCode@29 (line 1513)
mov %r11d,%edx
add $0xfffffff9,%edx
String::hashCodeの例 ※出力を整形しています
movzbl 0x18(%r13),%edi
;*iand {reexecute=0 rethrow=0 return_oop=0}
; - java.lang.StringLatin1::hashCode@31 (line 197)
; - java.lang.String::hashCode@27 (line 1533)
mov %ebp,%r12d
add $0xfffffff9,%r12d
JDK 12
JDK 13
© 2019 NTT DATA Corporation 37 #NTTDATATC
Math::min/max
• 元々はJavadocの仕様に未準拠の動きの是正
• ±0.0
• NaN
• 比較処理が細かくなったため、AVXを使うようになった
Haswellで25%、Skylake-Xで75%の性能向上
© 2019 NTT DATA Corporation 38 #NTTDATATC
JIT生成コードを眺めてみる
© 2019 NTT DATA Corporation 39 #NTTDATATC
hsdis
• HotSpotのJIT生成コードをディスアセンブル
• 公式にはバイナリ未配布
• OpenJDKのソースから自分でビルドして利用する
• 仮に配布されていても最新版をビルドするのがオススメ
• 新しい命令に対応していない可能性があるため
https://www.slideshare.net/YaSuenag/java-9-62345544/68
© 2019 NTT DATA Corporation 40 #NTTDATATC
hsdis for Linux
1. OpenJDKソースコードの入手
2. binutilsソースコードの入手
3. <OpenJDKソース>/src/utils/hsdisの下でmake
$ make BINUTILS=<binutilsソース> ARCH=amd64
© 2019 NTT DATA Corporation 41 #NTTDATATC
hsdis for Windows
1. WSLをインストール
– ディストロは何でもOK
※ここではUbuntu 18.04 LTSを例にします
2. MinGWのインストール
3. OpenJDKソースコードの入手
4. binutilsソースコードの入手
5. <OpenJDKソース>/src/utils/hsdisの下でmake
# apt-get install gcc-mingw-w64-x86-64
$ make BINUTILS=<binutilsソース> MINGW=x86_64-w64-mingw32
© 2019 NTT DATA Corporation 42 #NTTDATATC
配置
ビルドすると出来上がるライブラリをコピー
• Linux
build/linux-amd64/hsdis-amd64.soを
$JAVA_HOME/lib/server/へコピー
• Windows
build/windows-amd64/hsdis-amd64.dllを
%JAVA_HOME%¥bin¥serverへコピー
© 2019 NTT DATA Corporation 43 #NTTDATATC
実行
• –XX:+PrintAssembly
• JITにかかったコードすべてをディスアセンブル
• 出力が膨大
• –XX:CompilerDirectivesFile=<JSONファイル>
• Java 9から導入されたJITコンパイラの挙動指定方法
• https://docs.oracle.com/javase/jp/12/vm/writing-directives.html
• 後からjcmdで設定/変更が可能 [
{
“match”: “FMATest.calcFIRFMA”,
“PrintAssembly”: true
}
]
-XX:+UnlockDiagnosticVMOptionsに加えて
以下のいずれかを使用する
© 2019 NTT DATA Corporation 44 #NTTDATATC
まとめ
© 2019 NTT DATA Corporation 45 #NTTDATATC
まとめ
• Javaは継続的に進化
• 様々なチップベンダもOpenJDKの開発に参画
• JITコンパイラは新たなCPU命令を使って
プログラムをより効率的に動かそうとする
• 将来リリースされるCPU向け最適化も先取り
© 2019 NTT DATA Corporation 46 #NTTDATATC
まとめ
• 新しいJava&新しいCPUで幸せになれるかも
• CPUが古ければJITは新たな命令を使えない
• Javaが古ければJITは新たな命令を知らない
• 効果はプログラムの作りや動きに強く依存する
(何でも、必ず速くなるとは限らない)
• Java APIを優先的に使うと幸せになれるかも
• JavaクラスライブラリはHotSpotと密に連携している
• 最小/最大、配列操作のようなものでもAPI呼び出し限定の
JIT最適化が発生することがある
• 機械語レベルの最適化はロジックの最適化が終わってから!
© 2019 NTT DATA Corporation本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です。

JavaでCPUを使い倒す! ~Java 9 以降の CPU 最適化を覗いてみる~(NTTデータ テクノロジーカンファレンス 2019 講演資料、2019/09/05)

  • 1.
    © 2019 NTTDATA Corporation 1 © 2019 NTT DATA Corporation NTTデータ テクノロジーカンファレンス 2019 JavaでCPUを使い倒す! ~Java 9以降のCPU最適化を覗いてみる~ 2019年9月5日 株式会社NTTデータ 技術開発本部 先進基盤技術グループ 末永 恭正 #NTTDATATC
  • 2.
    © 2019 NTTDATA Corporation 2 #NTTDATATC “プログラムを動かす”ということ
  • 3.
    © 2019 NTTDATA Corporation 3 #NTTDATATC プログラムの表現方法は1つではない There’s more than one way to do it • Perlのモットー • Perlに限らず、どのプログラミング言語にも言えること public void sayHelloWorld(){ System.out.println(“Hello World”); } public void sayHelloWorld(){ var out = new FileOutputStream(FileDescriptor.out); try{ out.write(“Hello World¥n”.getBytes()); } catch(IOException e){ e.printStackTrace(); } }
  • 4.
    © 2019 NTTDATA Corporation 4 #NTTDATATC プログラムをCPUで動かす方法も1つではない 処理はCPUが行う 同じプログラムでもCPUの動かし方は何通りも存在する public void addArray(int[] a, int[] b, int[] result){ for(int i = 0; i < 8; i++){ result[i] = a[i] + b[i]; } } mov $7, %rcx # Loop index ([7] to [0]) 1: mov (%rax, %rcx, 4), %esi # Load a[i] add (%rbx, %rcx, 4), %esi # a[i] + b[i] mov %esi, (%rdi, %rcx, 4) # Store result sub $1, %rcx # Decrement index jns 1b vmovdqa (%rax), %ymm1 # Load a[] vpaddd (%rbx), %ymm1, %ymm2 # a[] + b[] vmovdqa %ymm2, (%rdi) # Store result
  • 5.
    © 2019 NTTDATA Corporation 5 #NTTDATATC CPUに”いい感じに”仕事してもらう • 効率のいいロジックを書く • 効率のいいAPI、ライブラリなどを利用する • コンパイラ最適化 • 様々な最適化手法 • 特定CPUにターゲティングした最適化 • プロファイリング • 動作状況把握(分岐、引数、コールパス、etc…) • JVMは実行時プロファイルに基づいて機械語を生成 • Just-In-Time: JIT
  • 6.
    © 2019 NTTDATA Corporation 6 #NTTDATATC Javaプログラムのコンパイル javac=バイトコード生成(≠機械語生成) • JVMが解釈可能なバイトコード「のみ」を生成 • JVMなしでは(OSが直接)実行できない • バイトコード最適化は意味がない可能性あり • JITによる最適化で効果が消える or 悪化する可能性あり • 一部処理(文字列結合など)はjavacが効率よいものへ 自動変換
  • 7.
    © 2019 NTTDATA Corporation 7 #NTTDATATC バイトコードの実行 HotSpot(OpenJDK / Oracle JDKのJVM)の場合 1. インタープリタ – バイトコードを逐次実行 – 実行状態をプロファイルして蓄積 2. JITコンパイル – 複数の最適化レベルに分けて機械語に翻訳(Tier 1~4) – メソッド全体、または一部を機械語へマルっと翻訳 – 処理によっては決まった機械語へマルっと置き換え 本日のテーマ
  • 8.
    © 2019 NTTDATA Corporation 8 #NTTDATATC JavaとCPU
  • 9.
    © 2019 NTTDATA Corporation 9 #NTTDATATC OpenJDKとチップベンダ 様々なチップベンダがOpenJDKコミュニティへパッチを寄贈 • Intel • AMD • Arm • Qualcomm • Huawei
  • 10.
    © 2019 NTTDATA Corporation 10 #NTTDATATC JavaとIntel製CPUのリリース時期 2014 2015 2016 2017 2018 2019 JDK 8 2014/03 JDK 9 2017/09 JDK 10 2018/03 JDK 11 2018/09 JDK 12 2019/03 JDK 13 2019/09 Broadwell 2014 Q3 Skylake 2015 Q3 Kaby Lake 2017 Q1 Coffee Lake 2017 Q4 Whiskey Lake 2018 Q3 Cascade Lake 2019 Q2 VNNIAVX-512 Ice Lake 2019 Q3
  • 11.
    © 2019 NTTDATA Corporation 11 #NTTDATATC JDK 8までのx86(拡張命令含む)への対応状況 • 暗号処理(AES-NI) • 同期処理(TSX) • SIMD • SSEやAVXを活用したJIT生成コードのベクタライズ • 文字列処理など、一部APIの処理置換 • Intelの最適化マニュアルに基づく改善
  • 12.
    © 2019 NTTDATA Corporation 12 #NTTDATATC OpenJDK 9 2017/09 – 2018/03 << EOL’ed >>
  • 13.
    © 2019 NTTDATA Corporation 13 #NTTDATATC JITのコード生成に関する主な改善内容 • SIMD命令サポート • JDK-8081247: AVX 512 extended support • JDK-8144771: Use AVX3 instructions for string compare • JDK-8154975: Update for vectorizedMismatch with AVX512 • JDK-8139340: SuperWord enhancement to support vector conditional move (CMovVD ) on Intel AVX cpu • 数学ライブラリの改善 • JDK-8135028: support for vectorizing double precision sqrt • JDK-8132207: update for x86 exp in the math lib • JDK-8139575: update for x86 log in the math lib • JDK-8145688: update for x86 pow in the math lib • JDK-8143353: update for x86 sin and cos in the math lib • JDK-8154122: Intrinsify fused mac operations on x86 • セキュリティ系ライブラリの改善 • JDK-8134553: CRC32C implementations for x86/x64 targets • JDK-8143925: enhancing CounterMode.crypt() for AESCrypt.implEncryptBlock() • JDK-8150767: Enables SHA Extensions on x86 • JDK-8152354: Update for x86 AES CBC Decryption • JDK-8154495: Update for x86 SHA256 using AVX2 • JDK-8165381: Update for x86 SHA512 using AVX2
  • 14.
    © 2019 NTTDATA Corporation 14 #NTTDATATC AVX 512への対応 • 512ビットレジスタを使ったIntelのSIMD命令 • 2016年からXeon Phiに導入 • 一部のハイエンド向けプロセッサへも2017年から導入 • AVX512BWやAVX512DQなど複数の仕様が存在 • プロセッサによって使える命令が異なる場合あり • Core i5など「一般向け」への導入は2019年から
  • 15.
    © 2019 NTTDATA Corporation 15 #NTTDATATC AVX 512:ループ処理のベクタライズ : vpaddd 0x50(%r8,%rbx,4),%ymm0,%ymm0 : vpaddd 0x70(%r8,%rbx,4),%ymm0,%ymm0 : : vpaddd 0x90(%r8,%rbp,4),%zmm0,%zmm0 : vpaddd 0xd0(%r8,%rbp,4),%zmm0,%zmm0 : public void addArray(int[] a, int[] b, int[] result){ for(int i = 0; i < a.length; i++){ result[i] = a[i] + b[i]; } } • ZMMレジスタを使った64バイトずつの処理 • ループ処理がより効率的に! 0x70-0x50=0x20(32バイト) 0xd0-0x90=0x40(64バイト) AVX 2 AVX 512
  • 16.
    © 2019 NTTDATA Corporation 16 #NTTDATATC AVX 512:文字列処理 longString.compareTo(anotherLongString); • SIMDを使った最適化でよく出てくるネタの1つ • JavaでもcompareTo()やindexOf()等のString操作は よく最適化される ランダムな文字列の比較で1.22倍の性能向上
  • 17.
    © 2019 NTTDATA Corporation 17 #NTTDATATC FMAへの対応 • Fused Multiply-Addの略 • 積和演算 • MAC(Multiply Accumulate)とも呼ばれる • 信号処理でよく出てくるパターン • C言語でもC99からfma()が導入 • 計算が速いだけでなく、浮動小数の丸め誤差の影響縮小 • Java 9からMath::fmaもAPIの仲間入り このメソッド呼び出しを最適化
  • 18.
    © 2019 NTTDATA Corporation 18 #NTTDATATC 例:FIRフィルタ 𝑦𝑦[𝑛𝑛] = � 𝑖𝑖=0 𝑁𝑁−1 𝐴𝐴 𝑖𝑖 𝐵𝐵[𝑛𝑛 − 𝑖𝑖] public double calcFIR(double[] a, double[] b){ int N = a.length; int n = N – 1; double result = 0.0d; for(int i = 0; i < N; i++) { result += a[i] * b[n – i]; } return result; } public double calcFIR(double[] a, double[] b){ int N = a.length; int n = N – 1; double result = 0.0d; for(int i = 0; i < N; i++) { result = Math.fma(a[i], b[n – i], result); } return result; } 普通に計算 FMA利用
  • 19.
    © 2019 NTTDATA Corporation 19 #NTTDATATC 例:FIRフィルタ result += a[i] * b[n – i]; result = Math.fma(a[i], b[n – i], result); vmulsd %xmm2,%xmm1,%xmm1 vaddsd %xmm0,%xmm1,%xmm1 vfmadd231sd %xmm2,%xmm1,%xmm0 JITコンパイル JITコンパイル 250112.3323524159 250112.33235242742 計算結果 計算結果 • 普通に計算した場合でもベクタライズされる • 普通に計算:2命令、FMA:1命令 • 計算結果も若干違う! 普通に計算 FMA利用 N=1,000,000、Randomで生成した配列の計算例
  • 20.
    © 2019 NTTDATA Corporation 20 #NTTDATATC SHA計算 • Secure Hash Algorithm • TLS通信などでおなじみ • ハッシュサイズに応じて仕様が複数存在 • プロセッサのSHA-NIサポートが必須 • ただし、SHA-256とSHA-512はAVX 2のみで最適化実施 • プログラムを変更しなくても性能が稼げる! • JDK付属のセキュリティプロバイダ(SUN)の利用が条件 MessageDigest.getInstance(“SHA-512”);
  • 21.
    © 2019 NTTDATA Corporation 21 #NTTDATATC OpenJDK 10 2018/03 – 2018/09 << EOL’ed >>
  • 22.
    © 2019 NTTDATA Corporation 22 #NTTDATATC JITのコード生成に関する主な改善内容 • SIMD命令サポート • JDK-8178811: Minimize the AVX <-> SSE transition penalty through generation of vzeroupper instruction on x86 • JDK-8192846: Support cmov vectorization for float • 数学ライブラリの改善 • JDK-8181616: FMA Vectorization on x86 • JDK-8190800: Support vectorization of Math.sqrt() on floats
  • 23.
    © 2019 NTTDATA Corporation 23 #NTTDATATC ペナルティの改善 Intelの最適化マニュアルより 意訳: AVX命令とSSE命令が連続して実行されるときはVZEROUPPERしようね!
  • 24.
    © 2019 NTTDATA Corporation 24 #NTTDATATC ペナルティの改善 ココをゼロクリア XMM (128bit) YMM (256bit) 2551280 • SSEレジスタとAVXレジスタは一部を共有 • レガシーSSE(VEXプリフィックスなし) 実行でCPUステートに関するペナルティ発生 • 上位128bitをクリアすることで ペナルティを回避 ※CPU世代によって異なります 127 SPECjvm 2008で3%の性能改善
  • 25.
    © 2019 NTTDATA Corporation 25 #NTTDATATC OpenJDK 11 2018/09 – << Long Term Support (LTS) >>
  • 26.
    © 2019 NTTDATA Corporation 26 #NTTDATATC JITのコード生成に関する主な改善内容 • SIMD命令サポート • JDK-8199421: Add support for vector popcount • JDK-8201193: Use XMM/YMM for objects initialization • セキュリティ系ライブラリの改善 • JDK-8205398: AES CBC decryption algorithm using AVX512 instructions
  • 27.
    © 2019 NTTDATA Corporation 27 #NTTDATATC オブジェクト初期化 • HotSpotはJavaオブジェクトの初期化にERMS (Enhanced REP MOVSB)を利用 • 最適化ガイドにも書いてある、妥当な方法 • glibcのmemcpy実装などでも使われている • ERMSをサポートしないプロセッサ向けにSSE/AVX命令 を使ったオブジェクト初期化が実装された • コントリビューターはAMD EPYC向けを意図 • Ivy Bridge以降のIntelプロセッサは影響なし
  • 28.
    © 2019 NTTDATA Corporation 28 #NTTDATATC 将来導入予定の命令への対応 • Intelの将来的な命令セット拡張リファレンスで Ice Lakeから導入が予定されている命令への対応 • VAES :AES CBC暗号の復号高速化 • VPOPCNTDQ :Integer::bitCountの高速化
  • 29.
    © 2019 NTTDATA Corporation 29 #NTTDATATC OpenJDK 12 2019/03 – 2019/09
  • 30.
    © 2019 NTTDATA Corporation 30 #NTTDATATC JITのコード生成に関する主な改善内容 • SIMD命令サポート • JDK-8210764: Update avx512 implementation • セキュリティ系ライブラリの改善 • JDK-8214074: Optimize Ghash using AVX instructions • その他、CPUを意識した修正 • JDK-8213479: Missing x86_64.ad patterns for 8-bit logical operators with destination in memory • JDK-8214751: X86: Support for VNNI Instructions
  • 31.
    © 2019 NTTDATA Corporation 31 #NTTDATATC VNNI • Vector Neural Network Instructions • Intel DL Boost • AVX 512の拡張 • ディープラーニングの推論性能を向上させる拡張命令 • Cascade Lake以降のプロセッサで利用可能
  • 32.
    © 2019 NTTDATA Corporation 32 #NTTDATATC VNNI public void mulAdd(short[] in1, short[] in2, int[] out){ for(int i = 0; i < N; i++){ out[i] += ((in1[2*i] * in2[2*i]) + (in1[2*i+1] * in2[2*i+1])); } } vpmaddwd %ymm0,%ymm1,%ymm0 vpaddd 0x10(%r13,%rbx,4),%ymm0,%ymm0 vpdpwssd %zmm1,%zmm2,%zmm0 • ZMMレジスタを使った64バイトずつの処理 • 掛けて足して(vpmaddwd)また足す(vpaddd)が VNNIが提供する1命令(vpdpwssd)に Whiskey Lake Cascade Lake
  • 33.
    © 2019 NTTDATA Corporation 33 #NTTDATATC OpenJDK 13 2019/09 – 2020/03
  • 34.
    © 2019 NTTDATA Corporation 34 #NTTDATATC JITのコード生成に関する主な改善内容 • SIMD命令サポート • JDK-8215483: Off heap memory accesses should be vectorized • 数学ライブラリの改善 • JDK-8217561: X86: Add floating-point Math.min/max intrinsics • その他、CPUを意識した修正 • JDK-8216580: Fix generation of VNNI vector code by allowing adjacent LoadS nodes to be isomorphic • JDK-8222074: Enhance auto vectorization for x86 • JDK-8217909: Make unused r12 register (without compressed oops) available to regalloc in C2
  • 35.
    © 2019 NTTDATA Corporation 35 #NTTDATATC レジスタの有効活用 • 圧縮Oopのヒープ情報保持目的に使っていたレジスタを 無効時に自由に使えるようにした修正 • -XX:UseCompressedOops • 32GB以下のヒープサイズで動作するポインタ圧縮機能 • 使えるレジスタが増える=ロード/ストアの効率化 • JDK 13のリリースノートにも掲載予定
  • 36.
    © 2019 NTTDATA Corporation 36 #NTTDATATC レジスタの有効活用 movzbl 0x18(%r9),%r8d ;*iand {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.StringLatin1::hashCode@31 (line 196) ; - java.lang.String::hashCode@29 (line 1513) mov %r11d,%edx add $0xfffffff9,%edx String::hashCodeの例 ※出力を整形しています movzbl 0x18(%r13),%edi ;*iand {reexecute=0 rethrow=0 return_oop=0} ; - java.lang.StringLatin1::hashCode@31 (line 197) ; - java.lang.String::hashCode@27 (line 1533) mov %ebp,%r12d add $0xfffffff9,%r12d JDK 12 JDK 13
  • 37.
    © 2019 NTTDATA Corporation 37 #NTTDATATC Math::min/max • 元々はJavadocの仕様に未準拠の動きの是正 • ±0.0 • NaN • 比較処理が細かくなったため、AVXを使うようになった Haswellで25%、Skylake-Xで75%の性能向上
  • 38.
    © 2019 NTTDATA Corporation 38 #NTTDATATC JIT生成コードを眺めてみる
  • 39.
    © 2019 NTTDATA Corporation 39 #NTTDATATC hsdis • HotSpotのJIT生成コードをディスアセンブル • 公式にはバイナリ未配布 • OpenJDKのソースから自分でビルドして利用する • 仮に配布されていても最新版をビルドするのがオススメ • 新しい命令に対応していない可能性があるため https://www.slideshare.net/YaSuenag/java-9-62345544/68
  • 40.
    © 2019 NTTDATA Corporation 40 #NTTDATATC hsdis for Linux 1. OpenJDKソースコードの入手 2. binutilsソースコードの入手 3. <OpenJDKソース>/src/utils/hsdisの下でmake $ make BINUTILS=<binutilsソース> ARCH=amd64
  • 41.
    © 2019 NTTDATA Corporation 41 #NTTDATATC hsdis for Windows 1. WSLをインストール – ディストロは何でもOK ※ここではUbuntu 18.04 LTSを例にします 2. MinGWのインストール 3. OpenJDKソースコードの入手 4. binutilsソースコードの入手 5. <OpenJDKソース>/src/utils/hsdisの下でmake # apt-get install gcc-mingw-w64-x86-64 $ make BINUTILS=<binutilsソース> MINGW=x86_64-w64-mingw32
  • 42.
    © 2019 NTTDATA Corporation 42 #NTTDATATC 配置 ビルドすると出来上がるライブラリをコピー • Linux build/linux-amd64/hsdis-amd64.soを $JAVA_HOME/lib/server/へコピー • Windows build/windows-amd64/hsdis-amd64.dllを %JAVA_HOME%¥bin¥serverへコピー
  • 43.
    © 2019 NTTDATA Corporation 43 #NTTDATATC 実行 • –XX:+PrintAssembly • JITにかかったコードすべてをディスアセンブル • 出力が膨大 • –XX:CompilerDirectivesFile=<JSONファイル> • Java 9から導入されたJITコンパイラの挙動指定方法 • https://docs.oracle.com/javase/jp/12/vm/writing-directives.html • 後からjcmdで設定/変更が可能 [ { “match”: “FMATest.calcFIRFMA”, “PrintAssembly”: true } ] -XX:+UnlockDiagnosticVMOptionsに加えて 以下のいずれかを使用する
  • 44.
    © 2019 NTTDATA Corporation 44 #NTTDATATC まとめ
  • 45.
    © 2019 NTTDATA Corporation 45 #NTTDATATC まとめ • Javaは継続的に進化 • 様々なチップベンダもOpenJDKの開発に参画 • JITコンパイラは新たなCPU命令を使って プログラムをより効率的に動かそうとする • 将来リリースされるCPU向け最適化も先取り
  • 46.
    © 2019 NTTDATA Corporation 46 #NTTDATATC まとめ • 新しいJava&新しいCPUで幸せになれるかも • CPUが古ければJITは新たな命令を使えない • Javaが古ければJITは新たな命令を知らない • 効果はプログラムの作りや動きに強く依存する (何でも、必ず速くなるとは限らない) • Java APIを優先的に使うと幸せになれるかも • JavaクラスライブラリはHotSpotと密に連携している • 最小/最大、配列操作のようなものでもAPI呼び出し限定の JIT最適化が発生することがある • 機械語レベルの最適化はロジックの最適化が終わってから!
  • 47.
    © 2019 NTTDATA Corporation本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です。