Your SlideShare is downloading. ×
0
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
ラムダと invokedynamic の蜜月
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

ラムダと invokedynamic の蜜月

5,398

Published on

0 Comments
31 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
5,398
On Slideshare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
39
Comments
0
Likes
31
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. ラムダと invokedynamic の蜜月 宮川 拓 / @miyakawa_taku 2013-07-22 JJUG ナイトセミナー
  • 2. 自己紹介 • 宮川 拓 (@miyakawa_taku) と申します • SI 屋です • JJUG 幹事です • Kink という JVM 言語を開発しています – https://bitbucket.org/kink/kink 1
  • 3. 要旨 • ラムダ式の実行は invokedynamic で実現さ れます。その理由と、実行の流れを見ます – 論点整理 – ラムダ式の実行 (1) – invokedynamic の復習 – ラムダ式の実行 (2) – なぜ invokedynamic? – ラムダの直列化 2 ※注記 この資料は、 JDK, JRE の「仕様」と「実装」を厳密に区別していません。
  • 4. 論点整理 3
  • 5. 静的構造 4 関数型インタフェース ラムダのクラス ラムダのインスタンス instance-of implements Comparable Comparable<String> c = (x, y) -> x.length() - y.length(); c c のクラス
  • 6. 実行の流れ 5 Comparable<String> c = (x, y) -> x.length() - y.length(); Collections.sort(strings, c); main Collections ラムダ sort compare / 処理の中身の実行 new / ラムダ式の実行 今回の主な論点
  • 7. ラムダ式の実行 (1) 6
  • 8. ラムダのクラスの実行時生成 • 匿名クラスがコンパイル時に生成されるのに 対し、ラムダのクラスは実行時に生成されます。 まずはそれを確かめます 7 関数型インタフェース ラムダのクラス ラムダのインスタンス instance-of implements 実行時に生成
  • 9. 匿名クラスをコンパイル • 匿名クラスは、「外側のクラス名$連番」という 名前で、コンパイラによって生成されます 8 $ cat >AnonClass.java import java.util.*; class AnonClass { Comparator<String> comparator() { return new Comparator<String> { @Override public int compare(String x, String y) { return x.length() – y.length(); } }; } } $ javac AnonClass.java && ls *.class AnonClass$1.class AnonClass.class
  • 10. ラムダをコンパイル • 同等のラムダをコンパイルしても、対応するク ラスファイルは生成されません 9 $ cat >Lambda.java import java.util.*; class Lambda { Comparator<String> comparator() { return (x, y) -> x.length() - y.length(); } } $ javac Lambda.java && ls *.class Lambda.class
  • 11. ラムダのクラスの生成タイミング • ラムダのクラスが、コンパイル時には生成され ないことが分かりました • したがって、実行時のどこかのタイミングで生 成されているはずです 10 ラムダを含む ソース クラス ファイル ラムダの クラスの生成 ラムダの インスタンス化 コンパイル (JDK) 実行 (JVM) この時点では ラムダのクラスは 生成されない どこかの タイミング
  • 12. ラムダのクラスの名前 • まずはラムダのクラスの名前を確認します 11 $ cat >Lambda.java import java.util.*; public class Lambda { public static void main(String[] args) { Comparator<String> c = (x, y) -> x.length() - y.length(); System.out.println(c.getClass()); } } $ javac Lambda.java && java Lambda class Lambda$$Lambda$1
  • 13. 生成のタイミング • loadClass(name) すると、ラムダ式を実行する タイミングでラムダのクラスが出現しているこ とが分かります 12 $ cat >Lambda.java ... System.out.println(loadClassOrNull("Lambda$$Lambda$1")); Comparator<String> c = (x, y) -> x.length() - y.length(); System.out.println(loadClassOrNull("Lambda$$Lambda$1")); ... $ javac Lambda.java && java Lambda null class Lambda$$Lambda$1
  • 14. ラムダ式実行の過程 • ラムダ式を実行するタイミングで、ラムダのク ラスが生成されていることが分かりました • ではラムダ式は、バイトコードのレベルでは、ど のような過程で実行されているのでしょうか? 13
  • 15. ラムダ式のバイトコード • ラムダ式を含むプログラムのクラスファイルを、 javap コマンドで逆アセンブルします 14 $ cat >Lambda.java import java.util.function.*; class Lambda { IntUnaryOperator adder(int delta) { return n -> n + delta; } } $ javap -c -p Lambda.class ... (次ページ) ...
  • 16. javap による逆アセンブルの結果 15 class Lambda { ... java.util.function.IntUnaryOperator adder(int); Code: 0: iload_1 1: invokedynamic #2, 0 6: areturn private static int lambda$0(int, int); Code: 0: iload_1 1: iload_0 2: iadd 3: ireturn }
  • 17. 再度 Java プログラム風に解釈 • ラムダの処理の中身は lambda$0 というメ ソッドに記述されます • ラムダ式の実行は invokedynamic 命令の呼 び出しになっています 16 class Lambda { IntUnaryOperator adder(int delta) { return <invokedynamic>(delta); } private static int lambda$0(int delta, int n) { return n + delta; } }
  • 18. ここまでの整理 • 分かったこと – ラムダのクラスはラムダ式実行の際に生成されま す – ラムダ式の実行は invokedynamic 命令です • 推定できること – invokedynamic 命令をきっかけとして、ラムダの クラスが生成され、またラムダのインスタンスが生 成されるはずです 17
  • 19. invokedynamic の復習 18
  • 20. invokedynamic の復習 • ラムダ式実行の流れを追いかけるにあたり、 まずは invokedynamic をおさらいします 19
  • 21. invokedynamic とは • 本来は、 JRuby など、 Java 以外の言語処理 系のために、 Java SE 7 で追加されたメソッド 呼び出し命令です • invokevirtual, invokeinterface など、 Java SE 6 までのメソッド呼び出し命令と異なり、呼び 出す処理が実行時に選択できます 20
  • 22. Java のメソッド呼び出し手順 • どの呼び出し命令でも、手順は大体同じです 21 int result = receiver.doSomething(arg0, arg1); receiver arg0 receiver arg1 arg0 receiver 戻り値 invokexxx レシーバと引数をスタックに積む 呼び出し 結果も スタックに void 以外の場合
  • 23. Java SE 6 までの呼び出し命令 • Java SE 6 までの呼び出し命令は、いずれも Java 言語と密に結びついてます – メソッドは再定義されない – 名前、引数の型、レシーバのクラスが決まれば、呼 び出すべき処理が定まる 22 invokestatic static メソッドを呼び出す invokespecial コンストラクタ、 private メソッド等を呼び出す invokevirtual クラスに属するメソッドを呼び出す invokeinterface インタフェースに属するメソッドを呼び出す
  • 24. Java 以外の言語処理系の実装 ―Java SE 6 以前 • Java 言語にない機構(メソッド再定義など)を 実現するため、処理系が呼び出しに介入 →JVM による実行時最適化が効きづらい 23 array.join invoke virtual 処理系 size Func@42 join Func@123 検索 def join ... invoke virtual 関数テーブル
  • 25. Java 以外の言語処理系の実装 ―Java SE 7 以降 • invokedynamic を使って、処理系を介さずに、 直接メソッドが呼び出せるようになりました →JVM による実行時最適化が効きやすい! 24 array.join invokedynamic def join ...
  • 26. invokedynamic の道具立て • 呼び出し元 (CallSite) ごとに、ブートストラップ メソッドで、呼び出し先の関数ポインタ (MethodHandle) を登録 25 Method Handle オブジェクト CallSite オブジェクト ブートストラップ メソッド <<create>> 初回呼び出しの前に 実行 対象の 処理 呼び出し元 <<create>> 呼び出し
  • 27. ブートストラップメソッド • static である必要がある • ブートストラップメソッドの引数 – Lookup: MethodHandle のファクトリ – String: 「メソッド名」だが、使わなくても可 – MethodType: invokedynamic の引数型と戻り値型 – 任意個数の定数 • ブートストラップメソッドの戻り値 – MethodHandle の初期値が紐付けられた CallSite 26
  • 28. ブートストラップメソッドの例 • メソッドを呼び出した後、強制的に戻り値を 42 にする MethodHandle を生成 27 static CallSite bsm(Lookup lu, String name, MethodType mt) throws Exception { MethodHandle vmh = lu.findVirtual(mt.parameterType(0), name, vmt); return new ConstantCallSite(filterReturnValue(vmh, dropArguments(constant(int.class, 42), 0, int.class))); } • 以上のように、個々の invokedynamic の動作は、 ブートストラップメソッドを見れば見当が付きます
  • 29. ラムダ式の実行 (2) 28
  • 30. ラムダ式の invokedynamic • 先ほど見たところでは、ラムダ式の実行は、 invokedynamic 命令の実行として実装され ていました →紐付けられているブートストラップメソッドを見 れば、実際の動作がわかるはずです 29
  • 31. ラムダ式の実行の流れ • ラムダ式の実行の invokedynamic には、 java.lang.invoke.LambdaMetaFactory の metaFactory メソッドがブートストラップメソッ ドとして紐付いています 30 invoke dynamic <<ブートストラップ>> LambdaMetaFactory #metaFactory ラムダの インスタンス化 2 回目以降の 実行 1. ラムダのクラスを生成 2. ラムダをインスタンス化する MethodHandle を生成
  • 32. LambdaMetaFactory #metaFactory 31 CallSite metafactory(Lookup lookup, // MethodHandle のファクトリ String name, // 関数型インタフェースの唯一の抽象メソッド (SAM) の名前 (例: compare) MethodType invokedType, // 命令の引数・戻り値型 MethodType samMethod, // SAM の引数・戻り値型 MethodHandle implMethod, // 処理本体のメソッド (例: lambda$0) MethodType instantiatedSamType) // 型パラメータ適用後の SAM の引数・戻り値型 シグネチャ 1. これらの情報を元にラムダのクラスを生成 • 引数をフィールドに格納するコンストラクタ • 処理本体のメソッドを呼び出す SAM の実装 2. ラムダをインスタンス化する MethodHandle を生成、 CallSite に紐付け
  • 33. 最終的に実行される処理 32 class Lambda { static class Lambda$1 implements IntUnaryOperator { private final int delta; Lambda$1(int delta) { this.delta = delta; } @Override public int applyAsInt(int n) { return lambda$0(this.delta, n); } } IntUnaryOperator adder(int delta) { return <invokedynamic: new Lambda$1(delta)>; } private static int lambda$0(int delta, int n) { return n + delta; } } metaFactory が生成 → 結局、やってることは匿名クラスと(ほぼ)同じ!
  • 34. なぜ invokedynamic? 33
  • 35. なぜ invokedynamic? • invokedynamic によるラムダ式の実行は、動 作としては匿名クラスと似たようなものでした • なぜ、わざわざ invokedynamic を使うので しょうか? →(1) クラスファイルが少なくなるおかげで、起動が 速くなる、かもしれません →(2) JVM が LambdaMetaFactory を独自に実装 することで、最適なインスタンス生成の方法を選 択できるようになります 34
  • 36. (1) 起動時間 • Java SE 8 では、 Streams API の採用によって、 プログラム中で全面的にラムダ式が利用され ることが想定されています(実態はどうあれ!) • その際、ラムダ式を匿名クラス方式で実装す ると、クラスファイルの数が飛躍的に増えるた め、クラスローディングが遅くなってしまいます • invokedynamic で、クラスを実行時に生成す れば、起動時間が抑えられる、かもしれません 35
  • 37. 匿名クラスとラムダ式の起動時間比較 • 5,000 個の匿名クラス/ラムダをインスタンス 化するプログラムを実行(各10回) 36 平均 2,041ms 2,375ms CPU: Core i3-2120T (2.6 GHz) OS: Arch Linux, カーネル: 3.9.9-1-ARCH JVM: JDK-8 build b99 (64-bit)
  • 38. 匿名クラス<ラムダ式 の考察 • 匿名クラスの実行時間増加要因 A) I/O B) jar の解凍 • ラムダ式の実行時間増加要因 C) ブートストラップメソッド呼び出し(それにともな う MethodHandle 作成など) D) バイトコード生成 37 A+B < C+D となった?
  • 39. (2) インスタンス生成戦略の選択 • LambdaMetaFactory は JVM が提供する API です。したがって、実行時に JVM に都合 のよい方法でインスタンスが生成できます • 可能な選択肢: – 1 つのラムダ式ごとに 1 つのクラスを生成(既述) – 外部の値を参照しないラムダ式であれば、シング ルトンインスタンスを戻す(後述) 38
  • 40. シングルトンインスタンス • 次のラムダ式は、外側の変数に依存していな いため、何度実行しても、同じ働きのインスタ ンスを戻します →この場合、シングルトンインスタンスを毎回使 い回せばいいはずです 39 Comparator<String> comparator() { return (x, y) -> x.length() - y.length(); }
  • 41. シングルトンインスタンス: 実行の流れ • ラムダの処理の本体が、外側の変数に依存し ていない場合、ラムダ式はシングルトンインス タンスを戻します 40 invoke dynamic <<ブートストラップ>> LambdaMetaFactory #metaFactory シングルトン インスタンス 2 回目以降の 実行 1. ラムダのクラスを生成 2. ラムダのシングルトンインスタンスを生成 3. シングルトンインスタンスを戻す MethodHandle を生成
  • 42. シングルトンインスタンス: 確認 41 $ cat >Lambda.java import java.util.*; public class Lambda { static Comparator<String> comparator() { return (x, y) -> x.length() - y.length(); } public static void main(String[] args) { System.out.println(comparator()); System.out.println(comparator()); System.out.println(comparator()); } } $ javac Lambda.java && java Lambda Lambda$$Lambda$1@84aee7 Lambda$$Lambda$1@84aee7 Lambda$$Lambda$1@84aee7
  • 43. その他の可能なインスタンス生成戦略 • 1 つの関数型インタフェースごとに 1 つのクラ スを生成。リフレクション経由で処理本体を呼 び出し • MethodHandle を関数型インタフェースに直 接ラップする機構を用意して、それを使う 42
  • 44. ラムダの直列化 43
  • 45. ラムダの直列化 • 関数型インタフェースが Serializable を拡張 している場合、ラムダのインスタンスは直列化 できる必要があります – 直列化(ラムダ→バイト列)、非直列化(バイト列→ ラムダ)した時、元のラムダと同じように機能する 必要がある • ラムダのクラスが実行時に生成される時、どう したら直列化・非直列化できるのでしょうか? → writeReplace / readResolve を使う 44
  • 46. 直列化の流れ 45 ラムダ インスタンス SerializedLambda バイト列 writeReplace インタフェース名、処理本体 のメソッド名など、ラムダを構 成する静的情報を保持 defaultWriteObject
  • 47. 非直列化の流れ 46 defaultReadObject ラムダ インスタンス SerializedLambdaバイト列 ★ ★: 静的情報を元にラムダを復元 SerializedLambda ラムダ式を含むクラス ラムダ readResolve $deserializeLambda$ <<create>> invokedynamic コンパイル時に生成
  • 48. 総括 47
  • 49. 総括 • ラムダ式は匿名クラスの単純な構文糖ではあ りません。 invokedynamic 命令を使って、クラ スを実行時に生成しています • これにより、 JVM がラムダのインスタンスの生 成方法を選べるので、実行時最適化の余地 が大きくなります 48
  • 50. 参考文献 49
  • 51. 参考文献 • Java SE 8 API Specification – http://download.java.net/jdk8/docs/api/overview-summary.html • JSR-335 – http://jcp.org/en/jsr/detail?id=335 • Brian Goetz “From Lambdas to Bytecode” – http://wiki.jvmlangsummit.com/images/1/1e/2011_Goetz_Lambd a.pdf • 宮川 拓「Lambda 式に invokedynamic を使うのかもしれな い話」 – http://d.hatena.ne.jp/miyakawa_taku/20120728/1343478485 50

×