Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

JEP280: Java 9 で文字列結合の処理が変わるぞ!準備はいいか!? #jjug_ccc

2,709 views

Published on

JJUG CCC 2017 Fall の LT 資料です。

Published in: Technology
  • Be the first to comment

JEP280: Java 9 で文字列結合の処理が変わるぞ!準備はいいか!? #jjug_ccc

  1. 1. Java 9 で 文字列結合の 処理が変わるぞ! 準備はいいか!? @YujiSoftware
  2. 2. 問題 • +演算子による文字列結合は最終的に どのような処理になる? private static String test(String str, int value) { return "ABC” + str + value; }
  3. 3. Java 8 までは • コンパイル時に StringBuilder を使った処理に なる private static String test(String str, int value) { return new StringBuilder() .append("ABC") .append(str) .append(value) .toString(); }
  4. 4. Java 9 では • InvokeDynamic で、実行時に処理を作る private static String test(String str, int value) { return InvokeDynamic #1:makeConcatWithConstants: (II)Ljava/lang/String; }
  5. 5. そして…
  6. 6. 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); }
  7. 7. +演算子による文字列結合で StringBuilder は 使わなくなった!
  8. 8. どういうこと? • JEP280 (Indy String Concatenation) の対応 – 再コンパイルなしで、文字列結合を最適な処理に 変更できるようにする InvokeDynamic を使って、実行時に処理を作るように 変更する – そのついでに、より最適化された処理を 作るようにしよう!(ストレッチゴール) いくつかの案を試すことになった
  9. 9. 実装された案 案(ストラテジー) 概要 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=(ストラテジー名) で変更可能
  10. 10. 採用された案 • MH_INLINE_SIZED_EXACT が採用された – 唯一、StringBuilder を使わない実装 • 特徴 – 各APIをMethodHandleでコールする処理を作る – 文字列を格納する配列の長さを、最初に厳密に 計算する • 足りなくなったら新しく配列を作り直す、という StringBuilderの無駄を避ける
  11. 11. 文字列結合の処理は どうやって作られるのか
  12. 12. 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;
  13. 13. さっぱり分からん (´・ω・`)
  14. 14. 作戦変更 • JIT Watch で、 makeConcatWithConstants メソッドにより生成された処理を確認 • JIT Watchとは? – ソースコードと、それをコンパイルしたバイトコード、さらに 実行時のアセンブラを並べて比較できるツール – どのような処理が実行されたのかがひと目でわかる
  15. 15. 生成された処理 0x000001df3b29f540: cmp $0xfa0a1f00,%r10d 0x000001df3b29f547: jg L002f 0x000001df3b29f54d: cmp $0xc4653600,%r10d 0x000001df3b29f554: 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,%ecx 0x000001df3b29f55f: jmp L0003 L0002: mov $0x2,%ecx L0003: 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,%r8d 0x000001df3b29f56b: add $0x5,%r8d ;*iadd {reexecute=0 rethrow=0 return_oop=0}
  16. 16. 時間がないので流れだけ 説明すると…
  17. 17. Phase 1. byte配列を作る 1. 結合する各文字列の長さを計算する 2. 合計分の長さのbyte配列を作成する byte[] buf = new byte[10] 例: “ABC” → 3 1357 → 5 obj → obj.toString().length() → 3
  18. 18. 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配列
  19. 19. Phase 3. 文字列にする 4. 作った配列を String にする new String(buf, coder); A B C 1 3 5 7 o b j 完成!
  20. 20. どのぐらい速くなったか • 文字列にもよるが、1倍~3倍程度速くなる • ただし、最初だけ少し遅い – InvokeDynamic で処理を作る必要があるため • 詳しくは、JEP280 の資料を参照 – http://cr.openjdk.java.net/~shade/8085796/notes .txt
  21. 21. まとめ • Java 9 で + 演算による文字列結合の処理が 変わった – StringBuilder が使われなくなった – パフォーマンスも向上した • Java 9 を使う理由がまた一つ増えた! • ぜひアップデートした知識を使って、Java を アップデートしましょう!
  22. 22. Java 9 で 文字列結合の 処理が変わるぞ! 準備はいいか!? @YujiSoftware
  23. 23. ちなみに • 後日、詳細をブログにまとめる予定 • 地平線に行くで検索 – http://d.hatena.ne.jp/chiheisen/

×