1 of 20

JRubyにおけるInvokeDynamic

利用パターンと性能評価

日本JRubyユーザ会 中村浩士(なひ)

@nahi nahi@ruby-lang.org

資料: http://bit.ly/jjug-indy-jruby

サンプル: http://bit.ly/jjug-indy-jruby-code

2 of 20

自己紹介

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

C/C++ (18年)Java (13)Ruby (13)

余暇のOSS開発: CRuby (8)JRuby (2)

soap4rhttpclient

3 of 20

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

最新リリース版は1.6.6(もうすぐ1.6.7

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

Open Source (CPL, GPL, LGPL)

開発開始から10

フルタイム開発者登場から5

4 of 20

本日のゴール

JRubyを題材に、InvokeDynamicの実例を見る

伝えたいこと

  • 熱い技術・JVMすごい
  • 言語処理系にはgame changer
  • 複雑すぎてまだ制御しきれてない感

5 of 20

InvokeDynamic機能のおさらい

呼び出し(invokedynamic)とメソッド(MH)を実行時にリンク

  • リンクするMHは初回実行時に検索する
  • リンクしたMHJVMがキャッシュして、従来の呼び出しと同様に扱う
  • リンクの無効化を軽量に行う仕組みがある

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

6 of 20

InvokeDynamic

利用パターン

MH juggling in JRuby

7 of 20

InvokeDynamicによる最適化のコツ

  • リンクするMHの検索を極力減らす

  • 呼び出し側の型(MethodType)と、呼ばれるMHの型が合うよう、MethodHandle APIMH合成。Javaコードは極力入れない。

呼び出し型の凡例:

Objectlongの2引数、Objectが戻り値

(Object self, long value)Object

8 of 20

JRuby InvokeDynamic利用パターン

1. 文字列リテラル

2. その他リテラル

3. 擬似定数

4. インスタンス変数

5. メソッド呼び出し

6. 算術演算呼び出し

9 of 20

1. 文字列リテラル

適用先: リテラル文字列

  • "Hello"bootstrapに渡すようバイトコード生成。
  • (ctx)Objectを、(ctx, str)Objectにリンクするため、str引数を挿入する関数を合成。

message = "Hello"message << name << "!"

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

&jrNewString(ctx, str):ターゲット

&insert(str = "Hello"):引数を1つ挿入

合成

ThreadContextは、Threadなど実行環境情報を格納したオブジェクト。どの呼び出しにも必須。

10 of 20

2. その他リテラル

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

  • 定数値はbootstrapに渡すようバイトコード生成。
  • 定数を返すMHを生成。
  • (ctx)Objectから()Objectにリンクするため、ctx引数を削る関数を合成。

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

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

&constant[10000]:常に10000を返すMH

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

11 of 20

3. 擬似定数

適用先: Rubyの定数参照

  • Rubyの定数は変更可能なため、「上書きされることの少ない変数」として扱う。
  • 上書き検出にSwitchPointを使う。

&thisMethod(ctx):同じMHを再設定(→値を再取得)

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

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

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

DEFAULT = Container.new.freezecomtainer = DEFAULT�DEFAULT = nil

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

12 of 20

4. インスタンス変数

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

  • 呼び出し側selfの変数テーブルを参照。
  • 変数テーブルはクラスにより異なる。
  • モジュールが他のクラスに�includeされている場合、�同じ"@cache"でも見るテーブルが違う。
  • クラスの切り替え判定にGuardWithTestを使う。

module Cache� def cache(value)� @cache = value� end�end�class Foo� include Cache�end�class Bar� include Cache�end

13 of 20

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

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

&thisMethod(self):同じMHを再設定(→selfの更新)

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

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

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

&jrTestClass(self):クラスが前回と同じかテスト

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

14 of 20

5. メソッド呼び出し

適用先: 任意のメソッド呼び出し

  • レシーバーのクラスに応じ呼び出し先が変わる。
  • レシーバークラスのメソッドが上書きされる可能性がある。
  • 引数の個数に応じて5つのタイプ。

$name = [:JRuby, "Ruby", 42]def process(router)router.say_hello($names.sample)�end

呼び出しの型:

(ctx, self)Object

(ctx, self, arg1)Object

(ctx, self, arg1, arg2)Object

(ctx, self, arg1, arg2, arg3)Object

(ctx, self, arg[])Object

15 of 20

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

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

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

&GuardWithTest:クラスが前回と違えば破棄

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

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

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

&thisMethod(ctx, self, arg1):再設定へ

&thisMethod(ctx, self, arg1):再設定へ

16 of 20

6. 算術演算呼び出し

適用先: 右辺が整数、小数の算術演算

  • 右辺のunboxをショートカットする。

fib(n - 2) + fib(n - 1)display if (x >= 5)�elapsed = msec * 1000.0

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

&jrInvoke(ctx, self, value):直接呼び出し

&jrTestClass(self):左辺は整数か

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

&drop(ctx):引数を1つ落とす

&GuardWithTest:左辺がFixnumならショートカット

17 of 20

JRuby + InvokeDynamic

性能評価

Hooray JVM!

18 of 20

マイクロベンチマーク

文字列リテラル

その他リテラル

擬似定数

インスタンス変数

メソッド呼び出し

算術演算

全て

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

※個々のパターンの処理のみを繰り返しループして測定。

19 of 20

二分木ベンチマーク

AVL木(再帰)

赤黒木(ループ)

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

20 of 20

InvokeDynamicまとめ

速い(ちゃんと使えば)

複雑(まだ使いこなせてない)

言語処理系が自身で最適化するよりは楽

→ 最適化はJVMに任せる

適用範囲の終わりはまだ見えない; 例えば

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