javaのlambdaの裏事情
DESCRIPTION
ぷろぐぱ ~プログラミング言語のパフォーマンスを考える~ 2013/12/7 福岡 でのプレゼン資料TRANSCRIPT
JavaのLambdaの裏事情
ぷろぐぱ~プログラミング言語のパフォーマンスを考える~
2013/12/7 きしだ なおき@kis
プログラミング言語が動くまで
● 字句解析– 文字が言語どの構成要素か解析
● 構文解析– 構成要素がどの構文要素か解析
● 実行エンジン– コンパイル言語の場合は実行ファイルを生成
● CPU– 実際に計算を行う
言語の構文がパフォーマンスに影響
● 動的型付言語は静的型付言語より遅い
● JavaScriptの配列は遅い
– インデックスが連続してなくてもいいため
● (という傾向がある)
とはいえ
● 動的型付言語も速くしたい● 静的型付言語も便利にしたら遅くなる● みんないろいろ工夫する
主な戦略
● だいたい難しいことしない● よくある処理が速くなると全体的に速くなる
ということでJavaの場合
Java言語とは
● 静的型付● オブジェクト指向
– というかクラス指向
● 事前コンパイル型
– Java VM用バイナリを生成
● Java VMで動く
実はJava-Javaコンパイラ
● 文字列でのswitch● 匿名クラス
文字列でのswitch
public static void main(String[] args) { switch(args[0]){ case "a": System.out.println("えー"); break; case "b": System.out.println("びー"); break; } }
public static void main(String[] args) { String str = args[0]; int h = str.hashCode(); int c = 2; switch(h){ case 97: if(str.equals("a")) c = 0; break; case 98: if(str.equals("b")) c = 1; break; } switch(c){ case 0: System.out.println("えー"); break; case 1: System.out.println("びー"); break; } }
匿名クラス public static void main(String[] args) { List<String> strs = Arrays.asList("a","b","c"); strs.sort(new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); } }); System.out.println(strs); }
public static void main(String[] args) { List<String> strs = Arrays.asList("a","b","c"); strs.sort(new Anonymous$1()); System.out.println(strs); }
private static class Anonymous$1 implements Comparator<String> { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); } }
Javaの最適化
● Java VMががんばる
– JITなど● ネイティブコードに実行時にコンパイル
● Java VMががんばれるコードをコンパイラが生成する
● Java VMががんばりやすい構文にする
JVMはJava言語だけのものじゃない
● 動的型
– JRuby
– Clojure
– JavaScript
– Jython● 静的型
– Scala
ところで圧倒的に動的型!
● Javaがそれなりに使えるので他の静的型言語が使われにくい– より強力な型じゃないと意味がない
● JavaVMにクセがあるので言語仕様で考慮しないと型の威力がでにくい
● ※未検証なので水島さんからツッコミがあると思います
Java VMのメソッド呼び出し(Java SE 6まで)
● invoke static– staticメソッド
– 常に同じものを呼び出す
– レシーバー(呼び出しオブジェクト)不要
● invoke special– コンストラクタやprivateメソッド
– 常に同じものを呼び出す
– レシーバー必要(以降同様)
● invoke virtual– クラスのインスタンスメソッド
– オブジェクトの型によってメソッドが異なる
● invoke interface– インタフェースのインスタンスメソッド
– オブジェクトの型によってメソッドが異なる
動的型には不向きだった
● Java VMはJava言語特化
● 動的型言語では呼び出しメソッドがコンパイル時に決めにくい– リフレクションの利用
– Java VMに頼らないメソッド呼び出し
Java SE 7で導入されたinvoke dynamic
● 動的型言語のため
● 実行時にメソッドの型を決めつつJava VMの最適化もかけやすくする
invoke dynamicの仕組み
● ブートストラップ– 実際に呼び出すメソッドを探す
– 呼び出すメソッドを指すMethodHandleを生成
– MethodHandleをCallSiteにくるんで返す
● CallSiteにくるまれたMethodHandleが指すメソッドを呼び出す
● 2回目はブートストラップを呼び出さない
● 呼び出し先が変わったらCallSiteが新たなMethodHandleを返す
ところでJavaにLambdaが来たよ
● (int a, int b) -> { return a + b; }● いろいろ省略できる
– (a, b) -> a + b;
関数型インタフェース
● 実装すべきメソッドがひとつのインタフェース
– Runnable ( runメソッド)
– Comparator( compareメソッド)
Lambda式
● Lambdaは関数型インタフェースを実装するクラスのインスタンスを生成するための構文
Comparator<String> comp = new Comparator<String>(){ @Override public int compare(String o1, String o2) { return o2.compareTo(o1); }};
Comparator<String> comp = (o1, o2) -> o2.compareTo(o1);
すっきり!
Lambdaの裏側
● クラスができていない● メソッドができている
● InvokeDynamic
なぜ匿名クラスじゃないのか
● クラスができまくる
– Streamなど使うとたくさんクラスができる
– しかもあまり使われない
● いちいちコンストラクタをよびだす
どのようにInvodeDynamicが使われるか
● ラムダオブジェクトの生成
– LambdaMetaFactory.metafactory– 関数型インタフェースを実装したクラスのインスタンスを生成す
るメソッドが返される● ついでにクラスもこの時点で作ったりする
– そのメソッドを呼び出してラムダオブジェクトを生成
● VMが変わるとより最適化されたインスタンスが生成される– 例:メソッドハンドルをラップする共通クラスのインスタンス
– 例:同じ式には同じインスタンス
結び
● 言語を実行するためにいろいろがんばってる
● InvokeDynamicは言語要素の最適な実装をあとまわしにできるおもしろい仕組み
● わかると楽しい
資料
● 「Lambda: A Peek Under The Hood - Brian Goetz」
– http://www.slideshare.net/jaxlondon2012/lambda-a-peek-under-the-hood-brian-goetz
● 「ラムダと invokedynamic の蜜月」
– http://www.slideshare.net/miyakawataku/lambda-meets-invokedynamic
● 「Java 7 invokedynamic の概要」
– http://www.slideshare.net/miyakawataku/java-7-invokedynamic-in-a-nutshell