Download - Java SE 7 InvokeDynamic in JRuby
Java SE 7 InvokeDynamicin JRuby
日本JRubyユーザ会 中村浩士@nahi [email protected]://github.com/nahi
http://slidesha.re/JavaOneJpInvokeDynamic
#jt12_s204
自己紹介
ネットワークセキュリティ関連のシステム開発
C/C++ (18年)、Java (13年)、Ruby (13年) 余暇のOSS開発
CRuby (8年) とJRuby (2年) のコミッタsoap4r、httpclient他の開発
Java SE 7 InvokeDynamicとは
Java SE 7に追加された新機能
変数に型のない動的型付け言語の性能向上支援 ● Java仮想マシン(JVM)のバイトコードに
invokedynamic命令を追加
● java.lang.invoke.*に関連APIを追加
JRubyとは - http://jruby.org/
最新リリース版は1.6.7InvokeDynamic対応は1.7から
(来月末のJRubyConfでPreviewリリース)
JVM上で動作するRuby(動的型付け言語)
Open Source (CPL, GPL, LGPL) 開発開始から10年
本日のゴール
InvokeDynamic機能について、実例となるソース
コード、バイトコードに基づく理解を得る JRubyにおける性能向上とコーディングパターンを
学ぶことで、JVMの新たな可能性を見る
Agenda
前半: InvokeDynamic機能解説
● Java言語用メソッド呼び出し● 動的型付け言語のメソッド呼び出し
● invokedynamic命令と関連API
後半: JRubyにおけるInvokeDynamicの活用● 利用パターン● 性能評価
InvokeDynamic機能解説
Java SE 7 InvokeDynamic in JRuby
#jt12_s204
Java言語のための仮想マシンバイトコード命令を逐次実行 必要に応じて最適化(JITコンパイル)
インライン化、ループ展開、ロック削除、デッドコード削除、エスケープ解析
JVMとは
JIT最適化の例: インライン化double addAllSqrts(int max) { double accum = 0; for (int i = 0; i < max; i++) { accum = addSqrt(accum, i); } return accum;}double addSqrt(double a, int b) { return a + Math.sqrt(b);}public static void main(String[] args) { for (int i = 0; i < 100000; ++i) { (new Target()).addAllSqrts(10); }}
JIT最適化の例: インライン化
% java -XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Target 66 1 Target::addAllSqrts (27 bytes)67 2 Target::addSqrt (8 bytes) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic)78 1 % Target::main @ 2 (28 bytes) @ 12 Target::<init> (5 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 17 Target::addAllSqrts (27 bytes) inline (hot) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
JIT最適化の例
% java -XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Target 66 1 Target::addAllSqrts (27 bytes)67 2 Target::addSqrt (8 bytes) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic)78 1 % Target::main @ 2 (28 bytes) @ 12 Target::<init> (5 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 17 Target::addAllSqrts (27 bytes) inline (hot) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
Math.sqrt呼び出しと加算をインライン化
addSqrtをコンパイル
double addSqrt(double a, int b) { return a + Math.sqrt(b);}
% java -XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Target 66 1 Target::addAllSqrts (27 bytes)67 2 Target::addSqrt (8 bytes) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic)78 1 % Target::main @ 2 (28 bytes) @ 12 Target::<init> (5 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 17 Target::addAllSqrts (27 bytes) inline (hot) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
mainをコンパイル forの中にあるaddAllSqrtsおよびその中身を全てインライン化
double addAllSqrts(int max) { double accum = 0; for (int i = 0; i < max; i++) { accum = addSqrt(accum, i); }}public static void main(String[] args) { for (int i = 0; i < 100000; ++i) { (new Target()).addAllSqrts(10); }}
% java -XX:+PrintCompilation \ -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Target 66 1 Target::addAllSqrts (27 bytes)67 2 Target::addSqrt (8 bytes) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic)78 1 % Target::main @ 2 (28 bytes) @ 12 Target::<init> (5 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 17 Target::addAllSqrts (27 bytes) inline (hot) @ 15 Target::addSqrt (8 bytes) inline (hot) @ 3 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
mainをコンパイル
OSR: On-stackreplacement
forの中にあるaddAllSqrtsおよびその中身を全てインライン化
double addAllSqrts(int max) { double accum = 0; for (int i = 0; i < max; i++) { accum = addSqrt(accum, i); }}public static void main(String[] args) { for (int i = 0; i < 100000; ++i) { (new Target()).addAllSqrts(10); }}
http://www.slideshare.net/CharlesNutter/redev-2011-jvm-jit-for-dummies-what-the-jvm-does-with-your-bytecode-when-youre-not-looking by Charles Oliver Nutter (JRuby co-lead)
JITについてより詳しく
Java言語用
メソッド呼び出し
InvokeDynamic機能解説
#jt12_s204
JVMでのメソッド呼び出し
public class Command { void processOptions(String[] options) { boolean result; for (String opt : options) { result = process(opt.concat("?!")); } } boolean process(String opt) { ... } void run() { String[] options = { "yes", "no", "maybe" }; processOptions(options); }}
public class Command { void processOptions(String[] options) { boolean result; for (String opt : options) { result = process(opt.concat("?!")); } } boolean process(String opt) { ... } void run() { String[] options = { "yes", "no", "maybe" }; processOptions(options); }}
JVMでのメソッド呼び出し
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 23: ldc #2 25: invokevirtual #3 28: invokevirtual #4
JVMでのメソッド呼び出し<Command>
process(opt.concat("?!"));
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 // forループ引数optから"yes"を入れる
23: ldc #2 25: invokevirtual #3 28: invokevirtual #4
JVMでのメソッド呼び出し"yes"
<Command>process(opt.concat("?!"));
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 // forループ引数optから"yes"を入れる
23: ldc #2 // 定数"?!"を入れる
25: invokevirtual #3 28: invokevirtual #4
JVMでのメソッド呼び出し"?!"
"yes"
<Command>process(opt.concat("?!"));
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 // forループ引数optから"yes"を入れる
23: ldc #2 // 定数"?!"を入れる
25: invokevirtual #3 // String.concat // スタックからループ引数と"?!"を取り出し
// 引数としてconcatを呼んで...
28: invokevirtual #4
JVMでのメソッド呼び出し"?!"
"yes"
<Command>process(opt.concat("?!"));
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 // forループ引数optから"yes"を入れる
23: ldc #2 // 定数"?!"を入れる
25: invokevirtual #3 // String.concat // スタックからループ引数と"?!"を取り出し
// 引数としてconcatを呼んで
// 戻り値をスタックに積む
28: invokevirtual #4
JVMでのメソッド呼び出し"yes?!"
<Command>process(opt.concat("?!"));
void processOptions(java.lang.String[]); 20: aload_0 // thisであるCommandをスタックに入れる
21: aload 5 // forループ引数optから"yes"を入れる
23: ldc #2 // 定数"?!"を入れる
25: invokevirtual #3 // String.concat // スタックからループ引数と"?!"を取り出し
// 引数としてconcatを呼んで
// 戻り値をスタックに積む
28: invokevirtual #4 // process // スタックからthisと戻り値文字列を取り出し
// 自身であるCommandのprocessを呼ぶ
JVMでのメソッド呼び出し"yes?!"
<Command>process(opt.concat("?!"));
"process"、"concat"等、呼び出す場所
Call Site
this.process
String#concat
コンパイル時: 呼び出し先の参照情報
メソッドが属するクラス: Stringメソッド名: "concat"メソッド型(引数と戻り値の型): (String;String)String
[本資料でのメソッド型の表記方法]
Objectとlongの2引数、Objectが戻り値
→ (Object;long)Object
Call Siteに必要な情報
コンパイル時: 呼び出し先の参照情報
メソッドが属するクラス: Stringメソッド名: "concat"メソッド型(引数と戻り値の型): (String;String)String
実行時
呼び出し先メソッドの実体: String#concatレシーバー / 引数オブジェクト: "yes" / "?!"戻り値オブジェクト / 発生例外: "yes?!"
Call Siteに必要な情報
参照先メソッドを決定する4種のバイトコード
invokestatic staticメソッドを直接リンク
invokespecial private/super/コンストラクタ
invokevirtual インスタンスメソッド検索 (virtual解決)
invokeinterface インターフェースメソッド検索 (interface解決)
Java言語用メソッド呼び出し命令
invokevirtualのメソッド呼び出し
コンパイル時:メソッドが属するクラス:
JTextComponentメソッド名: "getText"メソッド型: ()Void
実行時:呼び出し先メソッドの実体:
JTextField#getText
JComponent
+paint()
JTextComponent+paint()+getText()
JTextField
+paint()+getText()+getColumns()
JTextArea+paint()+getText()+getRows()+getColumns()
invokevirtualのメソッド呼び出し
コンパイル時:メソッドが属するクラス:
JTextComponentメソッド名: "getText"メソッド型: ()Void
実行時:呼び出し先メソッドの実体:
JTextField#getText
JComponent
+paint()
JTextComponent+paint()+getText()
JTextField
+paint()+getText()+getColumns()
JTextArea+paint()+getText()+getRows()+getColumns()
型安全存在しないメソッド、フィールドの参照がない
アクセスコントロール
private、default、protected、final制御
メソッドの確実なoverride JVMによる最適化
JVMのメソッド呼び出し命令の特徴
動的型付け言語の
メソッド呼び出し
InvokeDynamic機能解説
#jt12_s204
動的型付け言語のメソッド呼び出し
def process_options(options) for opt in options process(opt.concat("?!")) endend
mock = Object.newdef mock.concat(arg) "tested!"endoptions = ["yes", "no", mock]process_options(options)
呼び出し先の解決方法が異なるレシーバ、型、名前、引数の数などに依存デフォルト引数の補完
動的にメソッドが追加される再定義されることもある
呼び出し先が存在しない時に呼ぶメソッドメタプログラミング用途
動的型付け言語のメソッド呼び出しの特徴
JRuby独自のCall Site(呼び出し先の参照情報を格納)
参照先メソッドの検索も独自実装
例: Java SE 6用のJRuby実装
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2; aload 9aload_0invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
"process"CallSite
process(opt.concat("?!"))
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9aload_0invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
"process"CallSite
"concat"CallSite
process(opt.concat("?!"))
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
"process"CallSite
"concat"CallSite
"yes"
process(opt.concat("?!"))
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call; invokevirtual CallSite.call;
"process"CallSite
"concat"CallSite
"yes"
"?!"
process(opt.concat("?!"))
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call;
// スタックのCallSite情報を元に動的メソッド呼び出し(concat)invokevirtual CallSite.call;
process(opt.concat("?!"))
"concat"CallSite
"yes"
"?!"
"process"CallSite
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call;
// スタックのCallSite情報を元に動的メソッド呼び出し(concat)invokevirtual CallSite.call;
"process"CallSite
"yes?!"
process(opt.concat("?!"))
Java SE 6用のJRuby生成バイトコード
aload_0invokevirtual main.getCallSite1;
// "process"呼び出し用のCallSiteをスタックに入れる
aload_0invokevirtual main.getCallSite2;
// "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call;
// スタックのCallSite情報を元に動的メソッド呼び出し(concat)invokevirtual CallSite.call;
// 同じく動的メソッド呼び出し(process)
process(opt.concat("?!"))
"process"CallSite
"yes?!"
...invokevirtual getCallSite2aload 9aload_0invokevirtual getString0invokevirtual CallSite.call
def target(opt) process(opt.concat("?!"))end
CallSite
呼び出しメソッド検索メソッドキャッシュキャッシュミス判定
Invoker RubyString#concat
最終的にここを呼ぶ
引数の数、順序の調整デフォルト引数の補完
Java SE 6でのJRubyメソッド呼び出し
素朴な実装のままでは遅いので... ● メソッドキャッシュ / 無効化
● JITコンパイル○ バイトコード動的生成→読み込み○ 脱最適化
より詳しく: http://bit.ly/JRubyHackingGuide
JRuby独自の最適化
invokedynamic命令と
動的型付け言語用API(java.lang.invoke.*)
InvokeDynamic機能解説
#jt12_s204
...invokevirtual getCallSite2aload 9aload_0invokevirtual getString0invokevirtual CallSite.call
CallSite
呼び出しメソッド検索メソッドキャッシュキャッシュミス判定
Invoker RubyString#concat
最終的にここを呼ぶ
引数の数、順序の調整デフォルト引数の補完
Java SE 6でのJRubyメソッド呼び出し
InvokerCallSite
呼び出しメソッド検索メソッドキャッシュキャッシュミス判定
RubyString#concat
最終的にここを呼ぶ
引数の数、順序の調整デフォルト引数の補完
Java SE 7でのJRubyメソッド呼び出し
bootstrap MethodHandle API
...aload 9invokedynamic getStringinvokedynamic concat
invokedynamic
InvokeDynamic用生成バイトコード
aload_2 // thisであるCommandをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
invokedynamic getString [...] // 引数の"?!"を取り出す
invokedynamic concat (IRubyObject;IRubyObject)IRubyObject [...] // "concat"メソッドを動的呼び出し
invokedynamic process (IRubyObject;IRubyObject)IRubyObject [...] // "process"メソッドを動的呼び出し
process(opt.concat("?!"))
生成されるバイトコードの違い
独自のCall Siteはなくinvokedynamic命令で直接呼び出す
concatの呼び出し情報は以下
メソッド名: "concat"メソッド型: (IRubyObject;IRubyObject)IRubyObject "concat"という名前のメソッドを、2つのRubyオブジェクト(先頭レシーバ、引数1つ)と共に呼び出し戻り値を得る
...呼び出し先はどこ?
bootstrapメソッド
invokedynamic命令をよく見ると... invokedynamic concat(IRubyObject;IRubyObject)IRubyObject [...] invokedynamic process(IRubyObject;IRubyObject)IRubyObject [...]
bootstrapメソッド
invokedynamic命令をよく見ると... invokedynamic concat(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite)] invokedynamic process(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite)]
...bootstrapと呼ぶ初期化メソッドが登録されている
bootstrapメソッド
(Lookup;String;MethodType)CallSite初回実行時のみ呼ばれるユーザ定義メソッド Lookup: メソッド検索用オブジェクト
String: メソッド名 ("concat")MethodType: スタック上の引数オブジェクト型
CallSite: 検索した呼び出し先メソッドの参照
(MethodHandle)を格納
invokestatic staticメソッドを直接リンク
invokespecial private/super/コンストラクタ
invokevirtual インスタンスメソッド検索 (virtual解決)
invokeinterface インターフェースメソッド検索 (interface解決)
invokedynamic 動的MethodHandle検索 (bootstrapによる解決)
Java SE 7からのメソッド呼び出し命令
コンセプト 道具
Call Siteと呼出先の動的なリンク invokedynamic, CallSiteリンク先検索ロジックをプログラム可能 bootstrapメソッド参照 MethodHandle型の安全かつ自動的な変換 MethodType実行時の呼び出し先メソッド分岐 MethodHandle合成
動的型付け言語で、Javaの呼び出しと同じ最適化
Java SE 7 InvokeDynamicとは何か
Lookup#* クラス名と名前指定でMHを生成
MethodHandle#bindTo 第1引数のレシーバを固定
MethodHandles#* MHを合成して新たなMHを生成
insertArguments 引数を部分適用したMHを生成
guardWithTest test, then, else用の3MHを合成 して実行時に分岐するMH
SwitchPoint#guardWithTest より最適化された
true/false分岐のMH生成
SwitchPoint.invalidateAll sptの無効化
MethodHandle(MH)操作API
"n - 1"のリンク先は?最初に呼ばれたXInteger#minusにリンク
ただし毎回nの型 == XIntegerのチェックは必要
4つのMHを合成してCallSiteに設定
MH操作の例
def fib(n) if n < 2 n else fib(n - 2) + fib(n - 1) endend
呼出先MHを検索してCallSiteに再設定するメソッドのMH
引数1を部分適用したXInteger#minus(1)のMH引数nの型がXIntegerかテストするメソッドのMH
XInteger#minus(a)を実装したJavaメソッドのMH
MethodHandle test, minus, fallback, all;minus = lookup.findVirtual(XInteger.class, "minus", MethodType.methodType(XObject.class, long.class));minus = MethodHandles.insertArguments(minus, 1, 1);
呼出先MHを検索してCallSiteに再設定するメソッドのMH
引数1を部分適用したXInteger#minus(1)のMH引数nの型がXIntegerかテストするメソッドのMH
XInteger#minus(a)を実装したJavaメソッドのMH
1番目の引数に1Lを部分適用
MethodHandle test, minus, fallback, all;minus = lookup.findVirtual(XInteger.class, "minus", MethodType.methodType(XObject.class, long.class));minus = MethodHandles.insertArguments(minus, 1, 1);
fallback = lookup.findStatic(Utils.class, "fallback", MethodType.methodType(CallSite.class));fallback = fallback.bindTo(site);
呼出先MHを検索してCallSiteに再設定するメソッドのMH
引数1を部分適用したXInteger#minus(1)のMH引数nの型がXIntegerかテストするメソッドのMH
XInteger#minus(a)を実装したJavaメソッドのMH
MethodHandle test, minus, fallback, all;minus = lookup.findVirtual(XInteger.class, "minus", MethodType.methodType(XObject.class, long.class));minus = MethodHandles.insertArguments(minus, 1, 1);
fallback = lookup.findStatic(Utils.class, "fallback", MethodType.methodType(CallSite.class));fallback = fallback.bindTo(site);
test = lookup.findStatic(Utils.class, "testClass", MethodType.methodType(boolean.class, ..., ...));test = test.bindTo(self.getClass());all = MethodHandles.guardWithTest(test, minus, fallback);site.setTarget(all);
呼出先MHを検索してCallSiteに再設定するメソッドのMH
引数1を部分適用したXInteger#minus(1)のMH引数nの型がXIntegerかテストするメソッドのMH
XInteger#minus(a)を実装したJavaメソッドのMH
InvokeDynamicによる最適化のコツ
呼び出し側の型(MethodType)と、呼びたいMethodHandleの型が合うようMH合成
Javaコードは極力入れないテスト用コードも極力小さくシンプルに MHの再検索を極力減らす
JRubyにおける
InvokeDynamic利用パターン
InvokeDynamic機能の活用
#jt12_s204
JRuby InvokeDynamic利用パターン
1. 文字列リテラル
2. その他リテラル
3. 擬似定数
4. インスタンス変数
5. メソッド呼び出し
6. 算術演算呼び出し
1. 文字列リテラル
適用先: リテラル文字列
● "Hello"はbootstrapに渡すようバイトコード生成
● (ctx)Object を (ctx;str)Object にリンクするため、str引数
を挿入する関数を合成
message = "Hello"message << name << "!"
呼び出しの型: (ThreadContext ctx)Object
&newString(ctx, str):ターゲット
&insert(str = "Hello"):引数を1つ挿入
合成
※ThreadContextはThreadなど実行環境情報を格納したオブジェクト
文字列リテラル参照のインライン化
def target "Hello"end
idx = 0while idx < 50000 target idx += 1end
文字列リテラル参照のインライン化
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (12 bytes) inline (hot) @ 5 o.j...IndySupport::newString (10 bytes) inline (hot) @ 6 o.j.RubyString::newStringShared (22 bytes) inline (hot) @ 6 o.j.Ruby::getString (5 bytes) inline (hot) @ 11 o.j.RubyString::<init> (19 bytes) inline (hot) @ 4 o.j.RubyString::<init> (35 bytes) inline (hot) @ 3 o.j.RubyObject::<init> (7 bytes) inline (hot) @ 3 o.j.RubyBasicObject::<init> (42 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 30 o.j...isObjectSpaceEnabled (5 bytes) inline (hot) @ 38 o.j...addToObjectSpace (30 bytes) never executed
j.l.* == java.lang.*o.j.* == org.jruby.*
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (12 bytes) inline (hot) @ 5 o.j...IndySupport::newString (10 bytes) inline (hot) @ 6 o.j.RubyString::newStringShared (22 bytes) inline (hot) @ 6 o.j.Ruby::getString (5 bytes) inline (hot) @ 11 o.j.RubyString::<init> (19 bytes) inline (hot) @ 4 o.j.RubyString::<init> (35 bytes) inline (hot) @ 3 o.j.RubyObject::<init> (7 bytes) inline (hot) @ 3 o.j.RubyBasicObject::<init> (42 bytes) inline (hot) @ 1 java.lang.Object::<init> (1 bytes) inline (hot) @ 30 o.j...isObjectSpaceEnabled (5 bytes) inline (hot) @ 38 o.j...addToObjectSpace (30 bytes) never executed
文字列引数の挿入操作
リンクしたターゲット
この辺はJRubyの内部実装
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (12 bytes) inline (hot) @ 5 o.j...IndySupport::newString (10 bytes) inline (hot)
文字列引数の挿入操作
リンクしたターゲット
&newString(ctx, str):ターゲット
&insert(str = "Hello"):引数を1つ挿入
2. その他リテラル
適用先: 文字列以外の不変リテラル
● 定数値はbootstrapに渡すようバイトコード生成
● 定数を返すMHを生成
● (ctx)Object から ()Object にリンクするため、ctx引数を削
る関数を合成
times = 10000matcher = /[A-Z][a-z]*/
呼び出しの型: (ThreadContext ctx)Object
&constant[10000]:常に10000を返すMH&drop(ctx):引数を1つ削る
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 sun.invoke...Conversions::identity (2 bytes) inline (hot)
定数を返すMHにリンク
引数の削除操作その他リテラル参照のインライン化
invokedynamicの効果
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 sun.invoke...Conversions::identity (2 bytes) inline (hot) $file$method__0$RUBY$target::call (21 bytes) inline (hot) @ 17 $file::method__0$RUBY$target (9 bytes) inline (hot) @ 5 o.j...AbstractScript::getFixnum0 (11 bytes) inline (hot) @ 7 o.j...RuntimeCache::getFixnum (33 bytes) inline (hot)
Java SE 7でのインライン化結果
Java SE 6でのインライン化結果
3. 擬似定数
適用先: Rubyの定数参照
● Rubyの定数は変更可能なため、「上書きされることの少な
い変数」として扱う
● 上書き検出にSwitchPointを使う
&fallback(ctx):同じMHを再構築(→値を再取得)
&constant[obj]:現在の値を定数として返すMH
&drop(ctx):引数を1つ削る
&SwitchPoint(&,&):任意の定数が定義されたら破棄
DEFAULT = Container.new.freezecomtainer = DEFAULTDEFAULT = nil
呼び出しの型: (ThreadContext ctx)Object
擬似定数参照のインライン化
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (25 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (16 bytes) inline (hot) @ 2 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 j.l.invoke...CallSite::getTarget (5 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (5 bytes) inline (hot) @ 1 sun.inv...Conversions::identity (2 bytes) inline (hot) @ 10 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 21 j.l.invoke.MH::invokeExact (6 bytes) inline (hot) @ 2 sun.inv...Conversions::identity (2 bytes) inline (hot)
$file::method__0$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (25 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (16 bytes) inline (hot) @ 2 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 j.l.invoke...CallSite::getTarget (5 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (5 bytes) inline (hot) @ 1 sun.inv...Conversions::identity (2 bytes) inline (hot) @ 10 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 21 j.l.invoke.MH::invokeExact (6 bytes) inline (hot) @ 2 sun.inv...Conversions::identity (2 bytes) inline (hot)
SwitchPoint分岐
SwitchPointの無効化チェック
無効化されていたら再リンクメソッドへ
SwitchPointの内部
通常は定数を返す
4. インスタンス変数
適用先: インスタンス変数アクセス
● 呼び出し側selfの変数テーブルを参照
● 変数テーブルはクラスにより異なる
● モジュールが他のクラスに
includeされている場合、クラスにより
"@cache"のテーブル内位置が異なる
● クラスの切り替え判定にguardWithTestを使う
module Cache def cache(value) @cache = value endendclass Foo include Cacheendclass Bar include Other include Cacheend
4. インスタンス変数(続き)
&getVariable(ctx,obj,index):ターゲット
&fallback:新たなメソッド呼び出しMHをネスト
呼び出しの型: (ctx;Object self)Object
&insert(obj = self, index = 2):引数挿入
&filterRetval(&nullToNil):戻り値変換の合成
&test(self):クラスに変更がないかテスト
&guardWithTest(&,&,&):クラスが前回と違えば破棄
インスタンス変数のインライン化
$file::method__1$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (25 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...Linker::testRealClass (20 bytes) inline (hot) @ 5 o.j.RubyBasicObj::getMetaClass (5 bytes) inline (hot) @ 8 o.j.RubyClass::getRealClass (2 bytes) inline (hot) @ 10 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 21 j.l.invoke.MH::invokeExact (14 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (8 bytes) inline (hot) @ 10 o.j...RuntimeHelpers::nullToNil (10 bytes) inline (hot)
guardWithTestによる分岐
$file::method__1$RUBY$target (7 bytes) @ 1 j.l.invoke.MH::invokeExact (25 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...Linker::testRealClass (20 bytes) inline (hot) @ 5 o.j.RubyBasicObj::getMetaClass (5 bytes) inline (hot) @ 8 o.j.RubyClass::getRealClass (2 bytes) inline (hot) @ 10 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 21 j.l.invoke.MH::invokeExact (14 bytes) inline (hot) @ 3 j.l.invoke.MH::invokeExact (8 bytes) inline (hot) @ 10 o.j...RuntimeHelpers::nullToNil (10 bytes) inline (hot)
guardWithTest分岐
bindしておいたクラスとの比較
インスタンス変数テーブルの参照
4. インスタンス変数(続き)
&getVariable(ctx,obj,index):ターゲット
&fallback:新たなメソッド呼び出しMHをネスト
呼び出しの型: (ctx;Object self)Object
&insert(obj = self, index = 2):引数挿入
&filterRetval(&nullToNil):戻り値変換の合成
&test(self):クラスに変更がないかテスト
&guardWithTest(&,&,&):クラスが前回と違えば破棄
4. インスタンス変数(ネストの具体例)
&getVariable(index):ターゲット
&insert:テーブル序数として2を挿入
&filterRetval:戻り値がnullならnilに変換
&test(self):クラスはFooか?&guardWithTest:クラスが前回と違えば破棄
&fallback:新たなメソッド呼び出しMHをネスト
4. インスタンス変数(ネストの具体例)
&getVariable(index):ターゲット
&insert:テーブル序数として3を挿入
&filterRetval:戻り値がnullならnilに変換
&test(self):クラスはBarか?&guardWithTest:クラスが前回と違えば破棄
&getVariable(index):ターゲット
&fallback:新たなメソッド呼び出しMHをネスト
&insert:テーブル序数として2を挿入
&filterRetval:戻り値がnullならnilに変換
&test(self):クラスはFooか?&guardWithTest:クラスが前回と違えば破棄
5. メソッド呼び出し
適用先: 任意のメソッド呼び出し
● レシーバーのクラスに応じ呼び出し先が変わる
● レシーバークラスのメソッドが上書きされる可能性がある
● 引数の個数に応じて5つのタイプ
def process(router) router.say_hello("Ruby")end
呼び出しの型:(ctx;self)Object(ctx;self;arg1)Object(ctx;self;arg1;arg2)Object(ctx;self;arg1;arg2;arg3)Object(ctx;self;arg[])Object
&test(self):クラスは同じか
&drop(ctx, self):引数を二つ落とす
&guardWithTest:レシーバークラスが違えば破棄
&SwitchPoint:そのクラスでメソッドが定義されたら破棄
呼び出しの型: (ctx;self;arg1)Object
&target(ctx, self, arg1):ターゲット
&fallback:新たなメソッド呼び出しMHをネスト
&fallback:同じMHを再構築(→ターゲット更新)
5. メソッド呼び出し(続き)
...
メソッド呼び出しのインライン化
$file::method__1$RUBY$target (9 bytes) @ 3 j.l.invoke.MH::invokeExact (33 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (20 bytes) inline (hot) @ 2 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 j.l.invoke...CallSite::getTarget (5 bytes) inline (hot) @ 16 j.l.invoke.MH::invokeExact (5 bytes) inline (hot) @ 1 sun.inv...Conversions::identity (2 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (35 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...Linker::testMetaclass (17 bytes) inline (hot) @ 5 o.j...getMetaClass (5 bytes) inline (hot) @ 14 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 31 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 6 $file::method__0$RUBY$stub (7 bytes) inline (hot)
分岐のネスト
$file::method__1$RUBY$target (9 bytes) @ 3 j.l.invoke.MH::invokeExact (33 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (20 bytes) inline (hot) @ 2 j.l.invoke.MH::invokeExact (9 bytes) inline (hot) @ 2 j.l.invoke...CallSite::getTarget (5 bytes) inline (hot) @ 16 j.l.invoke.MH::invokeExact (5 bytes) inline (hot) @ 1 sun.inv...Conversions::identity (2 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (35 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...Linker::testMetaclass (17 bytes) inline (hot) @ 5 o.j...getMetaClass (5 bytes) inline (hot) @ 14 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 31 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 6 $file::method__0$RUBY$stub (7 bytes) inline (hot)
メソッド再定義用にSwitchPointのチェック
レシーバークラスのチェック
どちらもOKならリンク済みMHを呼び出し
6. 算術演算呼び出し
適用先: 右辺が整数、小数の算術演算
● 右辺のunboxをショートカットする
fib(n - 2) + fib(n - 1)display if (x >= 5)elapsed = msec * 1000.0
呼び出しの型: (ctx;self)Object
&invoke(ctx, self, value):直接呼び出し
&test(self):左辺は整数か
&fixnumMinusOne(ctx, self):ターゲット
&drop(ctx):引数を1つ落とす
&guardWithTest:左辺がFixnumならショートカット
算術演算呼び出しのインライン化
$file::method__0$RUBY$target (14 bytes) @ 8 j.l.invoke.MH::invokeExact (33 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...MathLinker::fixnumTest (20 bytes) inline (hot) @ 8 o.j.Ruby::isFixnumReopened (5 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (11 bytes) inline (hot) @ 7 o.j...fixnumOperatorFail (109 bytes) never executed @ 3 o.j...Linker::fixnum_op_minus_one (9 bytes) inline (hot) @ 5 o.j.RubyFixnum::op_minus_one (35 bytes) inline (hot)
算術演算呼び出しのインライン化
$file::method__0$RUBY$target (14 bytes) @ 8 j.l.invoke.MH::invokeExact (33 bytes) inline (hot) @ 5 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 3 o.j...MathLinker::fixnumTest (20 bytes) inline (hot) @ 8 o.j.Ruby::isFixnumReopened (5 bytes) inline (hot) @ 12 j.l.invoke.MH::invokeExact (10 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (7 bytes) inline (hot) @ 29 j.l.invoke.MH::invokeExact (11 bytes) inline (hot) @ 7 o.j...fixnumOperatorFail (109 bytes) never executed @ 3 o.j...Linker::fixnum_op_minus_one (9 bytes) inline (hot) @ 5 o.j.RubyFixnum::op_minus_one (35 bytes) inline (hot)
左辺はFixnumか?
-1専用メソッドを呼び出す
JRuby + InvokeDynamic性能評価
InvokeDynamic機能の活用
#jt12_s204
マイクロベンチマーク
文字列リテラル
その他リテラル
擬似定数
インスタンス変数
メソッド呼び出し
算術演算
全て
※高さはJava 7非indyを1としたときの速度比率。高いほうが速い。※個々のパターンの処理のみを繰り返しループして測定。
二分木ベンチマーク
AVL木(再帰) 赤黒木(ループ)
※高さはJava 7非indyを1としたときの速度比率。高いほうが速い。
Java SE 7 InvokeDynamic
動的型付け言語にとっては革命的複雑だが、言語処理系が自身で最適化するより楽
→ 以後、最適化はJVMに任せる方向へ
InvokeDynamic機能の適用対象候補
● プロファイラ・デバッガ● 関数合成によるロジック再利用
● etc.
#jt12_s204