java se 7 invokedynamic in jruby

90
Java SE 7 InvokeDynamic in JRuby 日本JRubyユーザ会 中村浩士 @nahi [email protected] https://github.com/nahi http://slidesha.re/JavaOneJpInvokeDynamic #jt12_s204

Upload: hiroshi-nakamura

Post on 10-May-2015

4.634 views

Category:

Technology


2 download

DESCRIPTION

JavaOne Tokyo 2012での発表スライド

TRANSCRIPT

Page 1: Java SE 7 InvokeDynamic in JRuby

Java SE 7 InvokeDynamicin JRuby

日本JRubyユーザ会 中村浩士@nahi [email protected]://github.com/nahi

http://slidesha.re/JavaOneJpInvokeDynamic

#jt12_s204

Page 2: Java SE 7 InvokeDynamic in JRuby

自己紹介

ネットワークセキュリティ関連のシステム開発

C/C++ (18年)、Java (13年)、Ruby (13年) 余暇のOSS開発

CRuby (8年) とJRuby (2年) のコミッタsoap4r、httpclient他の開発

Page 3: Java SE 7 InvokeDynamic in JRuby

Java SE 7 InvokeDynamicとは

Java SE 7に追加された新機能

変数に型のない動的型付け言語の性能向上支援 ● Java仮想マシン(JVM)のバイトコードに

invokedynamic命令を追加

● java.lang.invoke.*に関連APIを追加

Page 4: Java SE 7 InvokeDynamic in JRuby

JRubyとは - http://jruby.org/

最新リリース版は1.6.7InvokeDynamic対応は1.7から

(来月末のJRubyConfでPreviewリリース)

JVM上で動作するRuby(動的型付け言語)

Open Source (CPL, GPL, LGPL) 開発開始から10年

Page 5: Java SE 7 InvokeDynamic in JRuby

本日のゴール

InvokeDynamic機能について、実例となるソース

コード、バイトコードに基づく理解を得る JRubyにおける性能向上とコーディングパターンを

学ぶことで、JVMの新たな可能性を見る

Page 6: Java SE 7 InvokeDynamic in JRuby

Agenda

前半: InvokeDynamic機能解説

● Java言語用メソッド呼び出し● 動的型付け言語のメソッド呼び出し

● invokedynamic命令と関連API

後半: JRubyにおけるInvokeDynamicの活用● 利用パターン● 性能評価

Page 7: Java SE 7 InvokeDynamic in JRuby

InvokeDynamic機能解説

Java SE 7 InvokeDynamic in JRuby

#jt12_s204

Page 8: Java SE 7 InvokeDynamic in JRuby

Java言語のための仮想マシンバイトコード命令を逐次実行 必要に応じて最適化(JITコンパイル)

インライン化、ループ展開、ロック削除、デッドコード削除、エスケープ解析

JVMとは

Page 9: Java SE 7 InvokeDynamic in JRuby

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); }}

Page 10: Java SE 7 InvokeDynamic in JRuby

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)

Page 11: Java SE 7 InvokeDynamic in JRuby

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);}

Page 12: Java SE 7 InvokeDynamic in JRuby

% 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); }}

Page 13: Java SE 7 InvokeDynamic in JRuby

% 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); }}

Page 14: Java SE 7 InvokeDynamic in JRuby

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についてより詳しく

Page 15: Java SE 7 InvokeDynamic in JRuby

Java言語用

メソッド呼び出し

InvokeDynamic機能解説

#jt12_s204

Page 16: Java SE 7 InvokeDynamic in JRuby

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); }}

Page 17: Java SE 7 InvokeDynamic in JRuby

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でのメソッド呼び出し

Page 18: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 19: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 20: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 21: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 22: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 23: Java SE 7 InvokeDynamic in JRuby

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("?!"));

Page 24: Java SE 7 InvokeDynamic in JRuby

"process"、"concat"等、呼び出す場所

Call Site

this.process

String#concat

Page 25: Java SE 7 InvokeDynamic in JRuby

コンパイル時: 呼び出し先の参照情報

メソッドが属するクラス: Stringメソッド名: "concat"メソッド型(引数と戻り値の型): (String;String)String

[本資料でのメソッド型の表記方法]

Objectとlongの2引数、Objectが戻り値

→ (Object;long)Object

Call Siteに必要な情報

Page 26: Java SE 7 InvokeDynamic in JRuby

コンパイル時: 呼び出し先の参照情報

メソッドが属するクラス: Stringメソッド名: "concat"メソッド型(引数と戻り値の型): (String;String)String

実行時

呼び出し先メソッドの実体: String#concatレシーバー / 引数オブジェクト: "yes" / "?!"戻り値オブジェクト / 発生例外: "yes?!"

Call Siteに必要な情報

Page 27: Java SE 7 InvokeDynamic in JRuby

参照先メソッドを決定する4種のバイトコード

invokestatic staticメソッドを直接リンク

invokespecial private/super/コンストラクタ

invokevirtual インスタンスメソッド検索 (virtual解決)

invokeinterface インターフェースメソッド検索 (interface解決)

Java言語用メソッド呼び出し命令

Page 28: Java SE 7 InvokeDynamic in JRuby

invokevirtualのメソッド呼び出し

コンパイル時:メソッドが属するクラス:

JTextComponentメソッド名: "getText"メソッド型: ()Void

実行時:呼び出し先メソッドの実体:

JTextField#getText

JComponent

+paint()

JTextComponent+paint()+getText()

JTextField

+paint()+getText()+getColumns()

JTextArea+paint()+getText()+getRows()+getColumns()

Page 29: Java SE 7 InvokeDynamic in JRuby

invokevirtualのメソッド呼び出し

コンパイル時:メソッドが属するクラス:

JTextComponentメソッド名: "getText"メソッド型: ()Void

実行時:呼び出し先メソッドの実体:

JTextField#getText

JComponent

+paint()

JTextComponent+paint()+getText()

JTextField

+paint()+getText()+getColumns()

JTextArea+paint()+getText()+getRows()+getColumns()

Page 30: Java SE 7 InvokeDynamic in JRuby

型安全存在しないメソッド、フィールドの参照がない

アクセスコントロール

private、default、protected、final制御

メソッドの確実なoverride JVMによる最適化

JVMのメソッド呼び出し命令の特徴

Page 31: Java SE 7 InvokeDynamic in JRuby

動的型付け言語の

メソッド呼び出し

InvokeDynamic機能解説

#jt12_s204

Page 32: Java SE 7 InvokeDynamic in JRuby

動的型付け言語のメソッド呼び出し

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)

Page 33: Java SE 7 InvokeDynamic in JRuby

呼び出し先の解決方法が異なるレシーバ、型、名前、引数の数などに依存デフォルト引数の補完

動的にメソッドが追加される再定義されることもある

呼び出し先が存在しない時に呼ぶメソッドメタプログラミング用途

動的型付け言語のメソッド呼び出しの特徴

Page 34: Java SE 7 InvokeDynamic in JRuby

JRuby独自のCall Site(呼び出し先の参照情報を格納)

参照先メソッドの検索も独自実装

例: Java SE 6用のJRuby実装

Page 35: Java SE 7 InvokeDynamic in 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("?!"))

Page 36: Java SE 7 InvokeDynamic in JRuby

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("?!"))

Page 37: Java SE 7 InvokeDynamic in JRuby

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("?!"))

Page 38: Java SE 7 InvokeDynamic in JRuby

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("?!"))

Page 39: Java SE 7 InvokeDynamic in JRuby

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

Page 40: Java SE 7 InvokeDynamic in JRuby

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("?!"))

Page 41: Java SE 7 InvokeDynamic in JRuby

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?!"

Page 42: Java SE 7 InvokeDynamic in JRuby

...invokevirtual getCallSite2aload 9aload_0invokevirtual getString0invokevirtual CallSite.call

def target(opt) process(opt.concat("?!"))end

CallSite

呼び出しメソッド検索メソッドキャッシュキャッシュミス判定

Invoker RubyString#concat

最終的にここを呼ぶ

引数の数、順序の調整デフォルト引数の補完

Java SE 6でのJRubyメソッド呼び出し

Page 43: Java SE 7 InvokeDynamic in JRuby

素朴な実装のままでは遅いので... ● メソッドキャッシュ / 無効化

● JITコンパイル○ バイトコード動的生成→読み込み○ 脱最適化

より詳しく: http://bit.ly/JRubyHackingGuide

JRuby独自の最適化

Page 44: Java SE 7 InvokeDynamic in JRuby

invokedynamic命令と

動的型付け言語用API(java.lang.invoke.*)

InvokeDynamic機能解説

#jt12_s204

Page 45: Java SE 7 InvokeDynamic in JRuby

...invokevirtual getCallSite2aload 9aload_0invokevirtual getString0invokevirtual CallSite.call

CallSite

呼び出しメソッド検索メソッドキャッシュキャッシュミス判定

Invoker RubyString#concat

最終的にここを呼ぶ

引数の数、順序の調整デフォルト引数の補完

Java SE 6でのJRubyメソッド呼び出し

Page 46: Java SE 7 InvokeDynamic in JRuby

InvokerCallSite

呼び出しメソッド検索メソッドキャッシュキャッシュミス判定

RubyString#concat

最終的にここを呼ぶ

引数の数、順序の調整デフォルト引数の補完

Java SE 7でのJRubyメソッド呼び出し

bootstrap MethodHandle API

...aload 9invokedynamic getStringinvokedynamic concat

invokedynamic

Page 47: Java SE 7 InvokeDynamic in JRuby

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("?!"))

Page 48: Java SE 7 InvokeDynamic in JRuby

生成されるバイトコードの違い

独自のCall Siteはなくinvokedynamic命令で直接呼び出す

concatの呼び出し情報は以下

メソッド名: "concat"メソッド型: (IRubyObject;IRubyObject)IRubyObject "concat"という名前のメソッドを、2つのRubyオブジェクト(先頭レシーバ、引数1つ)と共に呼び出し戻り値を得る

...呼び出し先はどこ?

Page 49: Java SE 7 InvokeDynamic in JRuby

bootstrapメソッド

invokedynamic命令をよく見ると... invokedynamic concat(IRubyObject;IRubyObject)IRubyObject [...] invokedynamic process(IRubyObject;IRubyObject)IRubyObject [...]

Page 50: Java SE 7 InvokeDynamic in JRuby

bootstrapメソッド

invokedynamic命令をよく見ると... invokedynamic concat(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite)] invokedynamic process(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite)]

...bootstrapと呼ぶ初期化メソッドが登録されている

Page 51: Java SE 7 InvokeDynamic in JRuby

bootstrapメソッド

(Lookup;String;MethodType)CallSite初回実行時のみ呼ばれるユーザ定義メソッド Lookup: メソッド検索用オブジェクト

String: メソッド名 ("concat")MethodType: スタック上の引数オブジェクト型

CallSite: 検索した呼び出し先メソッドの参照

(MethodHandle)を格納

Page 52: Java SE 7 InvokeDynamic in JRuby

invokestatic staticメソッドを直接リンク

invokespecial private/super/コンストラクタ

invokevirtual インスタンスメソッド検索 (virtual解決)

invokeinterface インターフェースメソッド検索 (interface解決)

invokedynamic 動的MethodHandle検索 (bootstrapによる解決)

Java SE 7からのメソッド呼び出し命令

Page 53: Java SE 7 InvokeDynamic in JRuby

コンセプト 道具

Call Siteと呼出先の動的なリンク invokedynamic, CallSiteリンク先検索ロジックをプログラム可能 bootstrapメソッド参照 MethodHandle型の安全かつ自動的な変換 MethodType実行時の呼び出し先メソッド分岐 MethodHandle合成

動的型付け言語で、Javaの呼び出しと同じ最適化

Java SE 7 InvokeDynamicとは何か

Page 54: Java SE 7 InvokeDynamic in JRuby

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

Page 55: Java SE 7 InvokeDynamic in JRuby

"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

Page 56: Java SE 7 InvokeDynamic in JRuby

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を部分適用

Page 57: Java SE 7 InvokeDynamic in JRuby

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

Page 58: Java SE 7 InvokeDynamic in JRuby

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

Page 59: Java SE 7 InvokeDynamic in JRuby

InvokeDynamicによる最適化のコツ

呼び出し側の型(MethodType)と、呼びたいMethodHandleの型が合うようMH合成

Javaコードは極力入れないテスト用コードも極力小さくシンプルに MHの再検索を極力減らす

Page 60: Java SE 7 InvokeDynamic in JRuby

JRubyにおける

InvokeDynamic利用パターン

InvokeDynamic機能の活用

#jt12_s204

Page 61: Java SE 7 InvokeDynamic in JRuby

JRuby InvokeDynamic利用パターン

1. 文字列リテラル

2. その他リテラル

3. 擬似定数

4. インスタンス変数

5. メソッド呼び出し

6. 算術演算呼び出し

Page 62: Java SE 7 InvokeDynamic in JRuby

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など実行環境情報を格納したオブジェクト

Page 63: Java SE 7 InvokeDynamic in JRuby

文字列リテラル参照のインライン化

def target "Hello"end

idx = 0while idx < 50000 target idx += 1end

Page 64: Java SE 7 InvokeDynamic in 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

j.l.* == java.lang.*o.j.* == org.jruby.*

Page 65: Java SE 7 InvokeDynamic in 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の内部実装

Page 66: Java SE 7 InvokeDynamic in 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つ挿入

Page 67: Java SE 7 InvokeDynamic in JRuby

2. その他リテラル

適用先: 文字列以外の不変リテラル

● 定数値はbootstrapに渡すようバイトコード生成

● 定数を返すMHを生成

● (ctx)Object から ()Object にリンクするため、ctx引数を削

る関数を合成

times = 10000matcher = /[A-Z][a-z]*/

呼び出しの型: (ThreadContext ctx)Object

&constant[10000]:常に10000を返すMH&drop(ctx):引数を1つ削る

Page 68: Java SE 7 InvokeDynamic in JRuby

$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にリンク

引数の削除操作その他リテラル参照のインライン化

Page 69: Java SE 7 InvokeDynamic in JRuby

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でのインライン化結果

Page 70: Java SE 7 InvokeDynamic in JRuby

3. 擬似定数

適用先: Rubyの定数参照

● Rubyの定数は変更可能なため、「上書きされることの少な

い変数」として扱う

● 上書き検出にSwitchPointを使う

&fallback(ctx):同じMHを再構築(→値を再取得)

&constant[obj]:現在の値を定数として返すMH

&drop(ctx):引数を1つ削る

&SwitchPoint(&,&):任意の定数が定義されたら破棄

DEFAULT = Container.new.freezecomtainer = DEFAULTDEFAULT = nil

呼び出しの型: (ThreadContext ctx)Object

Page 71: Java SE 7 InvokeDynamic in JRuby

擬似定数参照のインライン化

$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)

Page 72: Java SE 7 InvokeDynamic in JRuby

$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の内部

通常は定数を返す

Page 73: Java SE 7 InvokeDynamic in JRuby

4. インスタンス変数

適用先: インスタンス変数アクセス

● 呼び出し側selfの変数テーブルを参照

● 変数テーブルはクラスにより異なる

● モジュールが他のクラスに

includeされている場合、クラスにより

"@cache"のテーブル内位置が異なる

● クラスの切り替え判定にguardWithTestを使う

module Cache def cache(value) @cache = value endendclass Foo include Cacheendclass Bar include Other include Cacheend

Page 74: Java SE 7 InvokeDynamic in JRuby

4. インスタンス変数(続き)

&getVariable(ctx,obj,index):ターゲット

&fallback:新たなメソッド呼び出しMHをネスト

呼び出しの型: (ctx;Object self)Object

&insert(obj = self, index = 2):引数挿入

&filterRetval(&nullToNil):戻り値変換の合成

&test(self):クラスに変更がないかテスト

&guardWithTest(&,&,&):クラスが前回と違えば破棄

Page 75: Java SE 7 InvokeDynamic in JRuby

インスタンス変数のインライン化

$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)

Page 76: Java SE 7 InvokeDynamic in JRuby

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しておいたクラスとの比較

インスタンス変数テーブルの参照

Page 77: Java SE 7 InvokeDynamic in JRuby

4. インスタンス変数(続き)

&getVariable(ctx,obj,index):ターゲット

&fallback:新たなメソッド呼び出しMHをネスト

呼び出しの型: (ctx;Object self)Object

&insert(obj = self, index = 2):引数挿入

&filterRetval(&nullToNil):戻り値変換の合成

&test(self):クラスに変更がないかテスト

&guardWithTest(&,&,&):クラスが前回と違えば破棄

Page 78: Java SE 7 InvokeDynamic in JRuby

4. インスタンス変数(ネストの具体例)

&getVariable(index):ターゲット

&insert:テーブル序数として2を挿入

&filterRetval:戻り値がnullならnilに変換

&test(self):クラスはFooか?&guardWithTest:クラスが前回と違えば破棄

&fallback:新たなメソッド呼び出しMHをネスト

Page 79: Java SE 7 InvokeDynamic in JRuby

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:クラスが前回と違えば破棄

Page 80: Java SE 7 InvokeDynamic in JRuby

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

Page 81: Java SE 7 InvokeDynamic in JRuby

&test(self):クラスは同じか

&drop(ctx, self):引数を二つ落とす

&guardWithTest:レシーバークラスが違えば破棄

&SwitchPoint:そのクラスでメソッドが定義されたら破棄

呼び出しの型: (ctx;self;arg1)Object

&target(ctx, self, arg1):ターゲット

&fallback:新たなメソッド呼び出しMHをネスト

&fallback:同じMHを再構築(→ターゲット更新)

5. メソッド呼び出し(続き)

...

Page 82: Java SE 7 InvokeDynamic in JRuby

メソッド呼び出しのインライン化

$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)

Page 83: Java SE 7 InvokeDynamic in JRuby

分岐のネスト

$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を呼び出し

Page 84: Java SE 7 InvokeDynamic in JRuby

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ならショートカット

Page 85: Java SE 7 InvokeDynamic in JRuby

算術演算呼び出しのインライン化

$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)

Page 86: Java SE 7 InvokeDynamic in JRuby

算術演算呼び出しのインライン化

$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専用メソッドを呼び出す

Page 87: Java SE 7 InvokeDynamic in JRuby

JRuby + InvokeDynamic性能評価

InvokeDynamic機能の活用

#jt12_s204

Page 88: Java SE 7 InvokeDynamic in JRuby

マイクロベンチマーク

文字列リテラル

その他リテラル

擬似定数

インスタンス変数

メソッド呼び出し

算術演算

全て

※高さはJava 7非indyを1としたときの速度比率。高いほうが速い。※個々のパターンの処理のみを繰り返しループして測定。

Page 89: Java SE 7 InvokeDynamic in JRuby

二分木ベンチマーク

AVL木(再帰) 赤黒木(ループ)

※高さはJava 7非indyを1としたときの速度比率。高いほうが速い。

Page 90: Java SE 7 InvokeDynamic in JRuby

Java SE 7 InvokeDynamic

動的型付け言語にとっては革命的複雑だが、言語処理系が自身で最適化するより楽

→ 以後、最適化はJVMに任せる方向へ

InvokeDynamic機能の適用対象候補

● プロファイラ・デバッガ● 関数合成によるロジック再利用

● etc.

#jt12_s204