jep280: java 9 で文字列結合の処理が変わるぞ!準備はいいか!? #jjug_ccc
TRANSCRIPT
Java 9で
文字列結合の処理が変わるぞ!
準備はいいか!?
@YujiSoftware
問題
• +演算子による文字列結合は最終的にどのような処理になる?
private static String test(String str, int value) {
return "ABC” + str + value;}
Java 8までは
• コンパイル時に StringBuilderを使った処理になる
private static String test(String str, int value) {
return new StringBuilder()
.append("ABC")
.append(str)
.append(value)
.toString();}
Java 9では
• InvokeDynamicで、実行時に処理を作る
private static String test(String str, int value) {
return InvokeDynamic#1:makeConcatWithConstants:
(II)Ljava/lang/String;}
そして…
InvokeDynamicの結果!
• 「byte配列を生成し、そこに文字を詰める処理」が出来上がる!
private static String test(String str, int value) {
int length = (文字列の長さを計算);
byte[] buf = new byte[length];
int index = buf.length;
index = StringConcatHelper.prepend(index, buf, coder, value);
index = StringConcatHelper.prepend(index, buf, coder, str);
index = StringConcatHelper.prepend(index, buf, coder, "ABC");
return new String(buf,coder);}
+演算子による文字列結合で
StringBuilderは使わなくなった!
どういうこと?
• JEP280 (Indy String Concatenation)の対応
–再コンパイルなしで、文字列結合を最適な処理に変更できるようにする
InvokeDynamicを使って、実行時に処理を作るように変更する
–そのついでに、より最適化された処理を作るようにしよう!(ストレッチゴール)
いくつかの案を試すことになった
実装された案案(ストラテジー) 概要
BC_SB StringBuilderを使ったバイトコードを生成する(既存と同様)
BC_SB_SIZED StringBuilderを使ったバイトコードを生成する。加えて、必要な配列のサイズを「推定」する。
BC_SB_SIZED_EXACT StringBuilderを使ったバイトコードを生成する。加えて、必要な配列のサイズを「正確に計算」する。
MH_SB_SIZED MethodHandleベースのジェネレータを使って、最終的にStringBuilderを呼び出す。加えて、必要な配列のサイズを「推定」する。
MH_SB_SIZED_EXACT MethodHandleベースのジェネレータを使って、最終的にStringBuilderを呼び出す。加えて、必要な配列のサイズを「正確に計算」する。
MH_INLINE_SIZED_EXACT MethodHandleベースのジェネレータを使って、独自にbyte配列を構築する。必要な配列のサイズを「正確に計算」する。
※起動オプション –D:java.lang.invoke.stringConcat=(ストラテジー名) で変更可能
採用された案
• MH_INLINE_SIZED_EXACTが採用された
–唯一、StringBuilderを使わない実装
• 特徴
–各APIをMethodHandleでコールする処理を作る
–文字列を格納する配列の長さを、最初に厳密に計算する
• 足りなくなったら新しく配列を作り直す、というStringBuilderの無駄を避ける
文字列結合の処理はどうやって作られるのか
java.lang.invoke.StringConcatFactory#makeConcatWithConstantsの実装
// Start building the combinator tree. The tree "starts" with (<parameters>)String, and "finishes"
// with the (int, byte[], byte)String in String helper. The combinators are assembled bottom-up,
// which makes the code arguably hard to read.
// Drop all remaining parameter types, leave only helper arguments:
MethodHandle mh;
mh = MethodHandles.dropArguments(NEW_STRING, 3, ptypes);
// Mix in prependers. This happens when (byte[], int, byte) = (storage, index, coder) is already
// known from the combinators below. We are assembling the string backwards, so "index" is the
// *ending* index.
for (RecipeElement el : recipe.getElements()) {
// Do the prepend, and put "new" index at index 1
mh = MethodHandles.dropArguments(mh, 2, int.class);
switch (el.getTag()) {
case TAG_CONST: {
Object cnst = el.getValue();
MethodHandle prepender = MethodHandles.insertArguments(prepender(cnst.getClass()), 3, cnst);
mh = MethodHandles.foldArguments(mh, 1, prepender,
2, 0, 3 // index, storage, coder
);
break;
}
case TAG_ARG: {
int pos = el.getArgPos();
MethodHandle prepender = prepender(ptypes[pos]);
mh = MethodHandles.foldArguments(mh, 1, prepender,
2, 0, 3, // index, storage, coder
4 + pos // selected argument
);
break;
}
さっぱり分からん
(´・ω・`)
作戦変更
• JIT Watch で、makeConcatWithConstants
メソッドにより生成された処理を確認
• JIT Watchとは?
– ソースコードと、それをコンパイルしたバイトコード、さらに実行時のアセンブラを並べて比較できるツール
– どのような処理が実行されたのかがひと目でわかる
生成された処理0x000001df3b29f540: cmp $0xfa0a1f00,%r10d0x000001df3b29f547: jg L002f0x000001df3b29f54d: cmp $0xc4653600,%r10d0x000001df3b29f554: jle L0050 ;*if_icmple {reexecute=0 rethrow=0 return_oop=0}
; - java.lang.Integer::stringSize@24 (line 542); - java.lang.StringConcatHelper::mixLen@2 (line 96); - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11; - java.lang.invoke.LambdaForm$BMH/400136488::reinvoke@48; - java.lang.invoke.LambdaForm$MH/1879034789::linkToTargetMethod@6; - Test::test@17 (line 15)
0x000001df3b29f55a: mov $0x8,%ecx0x000001df3b29f55f: jmp L0003
L0002: mov $0x2,%ecxL0003: inc %ecx ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - java.lang.Integer::stringSize@36 (line 541); - java.lang.StringConcatHelper::mixLen@2 (line 96); - java.lang.invoke.DirectMethodHandle$Holder::invokeStatic@11; - java.lang.invoke.LambdaForm$BMH/400136488::reinvoke@48; - java.lang.invoke.LambdaForm$MH/1879034789::linkToTargetMethod@6; - Test::test@17 (line 15)
L0004: mov %ecx,%r8d0x000001df3b29f56b: add $0x5,%r8d ;*iadd {reexecute=0 rethrow=0 return_oop=0}
; - java.lang.StringConcatHelper::mixLen@5 (line 96)
時間がないので流れだけ説明すると…
Phase 1. byte配列を作る
1. 結合する各文字列の長さを計算する
2. 合計分の長さのbyte配列を作成する
byte[] buf = new byte[10]
例:“ABC” → 31357 → 5obj → obj.toString().length() → 3
Phase 2. byte配列に文字列を詰める
3. 末尾の方から、byte配列に文字を詰めていく
–末尾から処理する理由は不明
• メモリのアクセス効率がいい?
A B C 1 3 5 7 o b j
“A B C”1 3 5 7
o b j
byte配列
Phase 3.文字列にする
4. 作った配列を Stringにする
new String(buf, coder);
A B C 1 3 5 7 o b j
完成!
どのぐらい速くなったか
• 文字列にもよるが、1倍~3倍程度速くなる
• ただし、最初だけ少し遅い
– InvokeDynamicで処理を作る必要があるため
• 詳しくは、JEP280 の資料を参照
– http://cr.openjdk.java.net/~shade/8085796/notes.txt
まとめ
• Java 9で + 演算による文字列結合の処理が変わった
– StringBuilderが使われなくなった
–パフォーマンスも向上した
• Java 9 を使う理由がまた一つ増えた!
• ぜひアップデートした知識を使って、Javaをアップデートしましょう!
Java 9で
文字列結合の処理が変わるぞ!
準備はいいか!?
@YujiSoftware
ちなみに
• 後日、詳細をブログにまとめる予定
•地平線に行くで検索– http://d.hatena.ne.jp/chiheisen/