#jt12_s204



Java SE 7 InvokeDynamic
        in JRuby
      日本JRubyユーザ会 中村浩士
     @nahi nahi@ruby-lang.org
     https://github.com/nahi

http://slidesha.re/JavaOneJpInvokeDynamic
自己紹介

ネットワークセキュリティ関連のシステム開発
 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.7
 InvokeDynamic対応は1.7から
 (来月末のJRubyConfでPreviewリリース)

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

Open Source (CPL, GPL, LGPL)

開発開始から10年
本日のゴール
InvokeDynamic機能について、実例となるソース
コード、バイトコードに基づく理解を得る

JRubyにおける性能向上とコーディングパターンを
学ぶことで、JVMの新たな可能性を見る
Agenda
前半: InvokeDynamic機能解説
 ● Java言語用メソッド呼び出し
 ● 動的型付け言語のメソッド呼び出し
 ● invokedynamic命令と関連API

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




InvokeDynamic機能解説
 Java SE 7 InvokeDynamic in JRuby
JVMとは

Java言語のための仮想マシン
バイトコード命令を逐次実行

必要に応じて最適化(JITコンパイル)
 インライン化、ループ展開、ロック削除、
 デッドコード削除、エスケープ解析
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最適化の例                   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) addSqrtをコンパイル
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                   Math.sqrt呼び出しと加算を
                    Target::addSqrt (8 bytes) inline (hot)
              @ 3                 インライン化
                     java.lang.Math::sqrt (5 bytes) (intrinsic)
         @ 1    java.lang.Object::<init> (1 bytes) inline (hot)
double addAllSqrts(int max) {
                         double accum = 0;
                         for (int i = 0; i < max; i++) {
                             accum = addSqrt(accum, i); }}
                     public static void main(String[] args) {
% java -XX:+PrintCompilation 
       -XX:+UnlockDiagnosticVMOptions 0; i < 100000; ++i) Target
                         for (int i = -XX:+PrintInlining {
                             (new Target()).addAllSqrts(10); }}
66 1   Target::addAllSqrts (27 bytes)
67 2   Target::addSqrt (8 bytes)
         @mainをコンパイル
           3                      forの中にあるaddAllSqrtsおよび
                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)
double addAllSqrts(int max) {
                         double accum = 0;
                         for (int i = 0; i < max; i++) {
                             accum = addSqrt(accum, i); }}
 OSR: On-stack       public static void main(String[] args) {
% java -XX:+PrintCompilation 
 replacement
       -XX:+UnlockDiagnosticVMOptions 0; i < 100000; ++i) Target
                         for (int i = -XX:+PrintInlining {
                             (new Target()).addAllSqrts(10); }}
66 1   Target::addAllSqrts (27 bytes)
67 2   Target::addSqrt (8 bytes)
         @mainをコンパイル
           3                      forの中にあるaddAllSqrtsおよび
                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についてより詳しく
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)
#jt12_s204


 Java言語用
メソッド呼び出し
InvokeDynamic機能解説
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);
  }
}
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);
  }
}
JVMでのメソッド呼び出し
                                           <Command>
void processOptions(java.lang.String[]);

      process(opt.concat("?!"));

    20:   aload_0          // thisであるCommandをスタックに入れる
    21:   aload         5
    23:   ldc           #2
    25:   invokevirtual #3




    28: invokevirtual #4
JVMでのメソッド呼び出し
                                             "yes"
void processOptions(java.lang.String[]);
                                           <Command>
      process(opt.concat("?!"));

    20:   aload_0          // thisであるCommandをスタックに入れる
    21:   aload         5 // forループ引数optから"yes"を入れる
    23:   ldc           #2
    25:   invokevirtual #3




    28: invokevirtual #4
JVMでのメソッド呼び出し
                                             "?!"
void processOptions(java.lang.String[]);
                                             "yes"
      process(opt.concat("?!"));
                                           <Command>
    20:   aload_0          // thisであるCommandをスタックに入れる
    21:   aload         5 // forループ引数optから"yes"を入れる
    23:   ldc           #2 // 定数"?!"を入れる
    25:   invokevirtual #3




    28: invokevirtual #4
JVMでのメソッド呼び出し
                                                 "?!"
void processOptions(java.lang.String[]);
                                                "yes"
      process(opt.concat("?!"));
                                              <Command>
    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?!"
void processOptions(java.lang.String[]);
                                              <Command>
      process(opt.concat("?!"));

    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?!"
void processOptions(java.lang.String[]);
                                           <Command>
      process(opt.concat("?!"));

    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を呼ぶ
Call Site
"process"、"concat"等、呼び出す場所

                   this.process


                   String#concat
Call Siteに必要な情報
コンパイル時: 呼び出し先の参照情報
  メソッドが属するクラス:     String
  メソッド名:           "concat"
  メソッド型(引数と戻り値の型): (String;String)String

  [本資料でのメソッド型の表記方法]
    Objectとlongの2引数、Objectが戻り値
    → (Object;long)Object
Call Siteに必要な情報
コンパイル時: 呼び出し先の参照情報
  メソッドが属するクラス:     String
  メソッド名:           "concat"
  メソッド型(引数と戻り値の型): (String;String)String

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

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

invokestatic    staticメソッドを直接リンク
invokespecial   private/super/コンストラクタ

invokevirtual   インスタンスメソッド検索
                (virtual解決)
invokeinterface インターフェースメソッド検索
                (interface解決)
invokevirtualのメソッド呼び出し
コンパイル時:                             JComponent
  メソッドが属するクラス:                   +paint()

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


実行時:
                            JTextField        JTextArea
  呼び出し先メソッドの実体:
                          +paint()          +paint()
    JTextField#getText    +getText()        +getText()
                          +getColumns()     +getRows()
                                            +getColumns()
invokevirtualのメソッド呼び出し
コンパイル時:                             JComponent
  メソッドが属するクラス:                   +paint()

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


実行時:
                            JTextField        JTextArea
  呼び出し先メソッドの実体:
                          +paint()          +paint()
    JTextField#getText    +getText()        +getText()
                          +getColumns()     +getRows()
                                            +getColumns()
JVMのメソッド呼び出し命令の特徴

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

アクセスコントロール
 private、default、protected、final制御
 メソッドの確実なoverride

JVMによる最適化
#jt12_s204


動的型付け言語の
メソッド呼び出し
InvokeDynamic機能解説
動的型付け言語のメソッド呼び出し

   def process_options(options)
     for opt in options
       process(opt.concat("?!"))
     end
   end

   mock = Object.new
   def mock.concat(arg)
     "tested!"
   end
   options = ["yes", "no", mock]
   process_options(options)
動的型付け言語のメソッド呼び出しの特徴

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

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

呼び出し先が存在しない時に呼ぶメソッド
 メタプログラミング用途
例: Java SE 6用のJRuby実装

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

参照先メソッドの検索も独自実装
Java SE 6用のJRuby生成バイトコード
     process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;         "process"CallSite
   // "process"呼び出し用のCallSiteをスタックに入れる
aload_0
invokevirtual main.getCallSite2;

aload 9
aload_0
invokevirtual main.getString0;

invokevirtual CallSite.call;

invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
     process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;         "concat"CallSite
   // "process"呼び出し用のCallSiteをスタックに入れる
                                         "process"CallSite
aload_0
invokevirtual main.getCallSite2;
   // "concat"呼び出し用のCallSiteをスタックに入れる
aload 9
aload_0
invokevirtual main.getString0;

invokevirtual CallSite.call;

invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
     process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;              "yes"
   // "process"呼び出し用のCallSiteをスタックに入れる
                                         "concat"CallSite
aload_0
invokevirtual main.getCallSite2;         "process"CallSite
   // "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0
invokevirtual main.getString0;

invokevirtual CallSite.call;

invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
      process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;                  "?!"
   // "process"呼び出し用のCallSiteをスタックに入れる
                                                 "yes"
aload_0
invokevirtual main.getCallSite2;             "concat"CallSite
   // "concat"呼び出し用のCallSiteをスタックに入れる       "process"CallSite
aload 9 // forループ引数optから"yes"を入れる
aload_0
invokevirtual main.getString0;   // 引数の"?!"

invokevirtual CallSite.call;

invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
      process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;                  "?!"
   // "process"呼び出し用のCallSiteをスタックに入れる
                                                 "yes"
aload_0
invokevirtual main.getCallSite2;             "concat"CallSite
   // "concat"呼び出し用のCallSiteをスタックに入れる       "process"CallSite
aload 9 // forループ引数optから"yes"を入れる
aload_0
invokevirtual main.getString0;   // 引数の"?!"

invokevirtual CallSite.call;
   // スタックのCallSite情報を元に動的メソッド呼び出し(concat)
invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
      process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;                "yes?!"
   // "process"呼び出し用のCallSiteをスタックに入れる
                                            "process"CallSite
aload_0
invokevirtual main.getCallSite2;
   // "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0
invokevirtual main.getString0;   // 引数の"?!"

invokevirtual CallSite.call;
   // スタックのCallSite情報を元に動的メソッド呼び出し(concat)
invokevirtual CallSite.call;
Java SE 6用のJRuby生成バイトコード
      process(opt.concat("?!"))
aload_0
invokevirtual main.getCallSite1;                "yes?!"
   // "process"呼び出し用のCallSiteをスタックに入れる
                                            "process"CallSite
aload_0
invokevirtual main.getCallSite2;
   // "concat"呼び出し用のCallSiteをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
aload_0
invokevirtual main.getString0;   // 引数の"?!"

invokevirtual CallSite.call;
   // スタックのCallSite情報を元に動的メソッド呼び出し(concat)
invokevirtual CallSite.call;
   // 同じく動的メソッド呼び出し(process)
Java SE 6でのJRubyメソッド呼び出し
           def target(opt)
             process(opt.concat("?!"))
           end
                                         引数の数、順序の調整
                                         デフォルト引数の補完



                                                    RubyString#
                              CallSite    Invoker
...                                                 concat
invokevirtual getCallSite2                            最終的に
aload 9                                               ここを呼ぶ
aload_0                           呼び出しメソッド検索
invokevirtual getString0          メソッドキャッシュ
invokevirtual CallSite.call       キャッシュミス判定
JRuby独自の最適化

素朴な実装のままでは遅いので...

● メソッドキャッシュ / 無効化
● JITコンパイル
  ○ バイトコード動的生成→読み込み
  ○ 脱最適化

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

invokedynamic命令と
動的型付け言語用API
(java.lang.invoke.*)
   InvokeDynamic機能解説
Java SE 6でのJRubyメソッド呼び出し


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


...
                                                    RubyString#
invokevirtual getCallSite2    CallSite    Invoker
                                                    concat
aload 9
aload_0                                               最終的に
invokevirtual getString0                              ここを呼ぶ
invokevirtual CallSite.call       呼び出しメソッド検索
                                  メソッドキャッシュ
                                  キャッシュミス判定
Java SE 7でのJRubyメソッド呼び出し

                              呼び出しメソッド検索
                              メソッドキャッシュ             引数の数、順序の調整
                              キャッシュミス判定             デフォルト引数の補完
                                                         MethodHandle
                               bootstrap
                                                             API


                                                             RubyString#
                               CallSite        Invoker
                                                             concat
...                                       ic
                                  edy nam                       最終的に
                           nvok
aload 9
invokedynamic getString   i                                     ここを呼ぶ
invokedynamic concat
InvokeDynamic用生成バイトコード
        process(opt.concat("?!"))

aload_2 // thisであるCommandをスタックに入れる
aload 9 // forループ引数optから"yes"を入れる
invokedynamic getString [...]
  // 引数の"?!"を取り出す

invokedynamic concat (IRubyObject;IRubyObject)IRubyObject [...]
  // "concat"メソッドを動的呼び出し

invokedynamic process (IRubyObject;IRubyObject)IRubyObject
[...]
  // "process"メソッドを動的呼び出し
生成されるバイトコードの違い
独自の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)を格納
Java SE 7からのメソッド呼び出し命令

invokestatic    staticメソッドを直接リンク
invokespecial   private/super/コンストラクタ
invokevirtual   インスタンスメソッド検索
                (virtual解決)
invokeinterface インターフェースメソッド検索
                (interface解決)
invokedynamic 動的MethodHandle検索
                (bootstrapによる解決)
Java SE 7 InvokeDynamicとは何か
コンセプト                  道具
Call Siteと呼出先の動的なリンク   invokedynamic, CallSite
リンク先検索ロジックをプログラム可能     bootstrap
メソッド参照                 MethodHandle
型の安全かつ自動的な変換           MethodType
実行時の呼び出し先メソッド分岐        MethodHandle合成


動的型付け言語で、Javaの呼び出しと同じ最適化
MethodHandle(MH)操作API
Lookup#*              クラス名と名前指定でMHを生成
MethodHandle#bindTo   第1引数のレシーバを固定
MethodHandles#*       MHを合成して新たなMHを生成
   insertArguments    引数を部分適用したMHを生成
   guardWithTest      test, then, else用の3MHを合成
                      して実行時に分岐するMH

SwitchPoint#guardWithTest     より最適化された
                              true/false分岐のMH生成
SwitchPoint.invalidateAll     sptの無効化
def fib(n)
                     if n < 2
MH操作の例                 n
                     else
                       fib(n - 2) + fib(n - 1)
                     end
"n - 1"のリンク先は?     end

最初に呼ばれたXInteger#minusにリンク
ただし毎回nの型 == XIntegerのチェックは必要

4つのMHを合成してCallSiteに設定
引数nの型がXIntegerかテストするメソッドのMH
  引数1を部分適用したXInteger#minus(1)のMH
    XInteger#minus(a)を実装したJavaメソッドのMH
  呼出先MHを検索してCallSiteに再設定するメソッドのMH
引数nの型がXIntegerかテストするメソッドのMH
  引数1を部分適用したXInteger#minus(1)のMH
    XInteger#minus(a)を実装したJavaメソッドのMH
  呼出先MHを検索してCallSiteに再設定するメソッドのMH
MethodHandle test, minus, fallback, all;
minus = lookup.findVirtual(XInteger.class, "minus",
    MethodType.methodType(XObject.class, long.class));
minus = MethodHandles.insertArguments(minus, 1, 1);


                                        1番目の引数に
                                        1Lを部分適用
引数nの型がXIntegerかテストするメソッドのMH
  引数1を部分適用したXInteger#minus(1)のMH
    XInteger#minus(a)を実装したJavaメソッドのMH
  呼出先MHを検索してCallSiteに再設定するメソッドの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);
引数nの型がXIntegerかテストするメソッドのMH
  引数1を部分適用したXInteger#minus(1)のMH
    XInteger#minus(a)を実装したJavaメソッドのMH
  呼出先MHを検索してCallSiteに再設定するメソッドの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);
InvokeDynamicによる最適化のコツ

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

Javaコードは極力入れない
テスト用コードも極力小さくシンプルに

MHの再検索を極力減らす
#jt12_s204

 JRubyにおける
InvokeDynamic
  利用パターン
InvokeDynamic機能の活用
JRuby InvokeDynamic利用パターン

1. 文字列リテラル
2. その他リテラル
3. 擬似定数
4. インスタンス変数
5. メソッド呼び出し
6. 算術演算呼び出し
1. 文字列リテラル                  message = "Hello"
                            message << name << "!"

適用先: リテラル文字列
● "Hello"はbootstrapに渡すようバイトコード生成
● (ctx)Object を (ctx;str)Object にリンクするため、str引数
  を挿入する関数を合成
       ※ThreadContextはThreadなど実行環境情報を格納したオブジェクト



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

    &insert(str = "Hello"):引数を1つ挿入
       &newString(ctx, str):ターゲット
  合成
文字列リテラル参照のインライン化

     def target
       "Hello"
     end

     idx = 0
     while idx < 50000
       target
       idx += 1
     end
文字列リテラル参照のインライン化
$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             この辺はJRubyの内部実装
               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
文字列引数の挿入操作

                                          リンクしたターゲット
$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)



 &insert(str = "Hello"):引数を1つ挿入
    &newString(ctx, str):ターゲット
times = 10000
2. その他リテラル            matcher = /[A-Z][a-z]*/


適用先: 文字列以外の不変リテラル
● 定数値はbootstrapに渡すようバイトコード生成
● 定数を返すMHを生成
● (ctx)Object から ()Object にリンクするため、ctx引数を削
  る関数を合成


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

     &drop(ctx):引数を1つ削る
        &constant[10000]:常に10000を返すMH
その他リテラル参照のインライン化
                                              引数の削除操作
$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)

                               Java SE 7でのインライン化結果

$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 6でのインライン化結果
DEFAULT = Container.new.freeze
3. 擬似定数             comtainer = DEFAULT
                    DEFAULT = nil

適用先: Rubyの定数参照
● Rubyの定数は変更可能なため、「上書きされることの少な
    い変数」として扱う
●   上書き検出にSwitchPointを使う

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

&SwitchPoint(&,&):任意の定数が定義されたら破棄
   &drop(ctx):引数を1つ削る
       &constant[obj]:現在の値を定数として返すMH
    &fallback(ctx):同じMHを再構築(→値を再取得)
擬似定数参照のインライン化
$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分岐

$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の無効化チェック
                       通常は定数を返す

  無効化されていたら再リンクメソッドへ
module Cache
4. インスタンス変数                 def cache(value)
                              @cache = value
                            end
適用先: インスタンス変数アクセス         end
● 呼び出し側selfの変数テーブルを参照     class Foo
                            include Cache
● 変数テーブルはクラスにより異なる        end
                          class Bar
● モジュールが他のクラスに              include Other
  includeされている場合、クラスにより     include Cache
                          end
  "@cache"のテーブル内位置が異なる
● クラスの切り替え判定にguardWithTestを使う
4. インスタンス変数(続き)

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

&guardWithTest(&,&,&):クラスが前回と違えば破棄
   &test(self):クラスに変更がないかテスト
   &filterRetval(&nullToNil):戻り値変換の合成
      &insert(obj = self, index = 2):引数挿入
         &getVariable(ctx,obj,index):ターゲット
   &fallback:新たなメソッド呼び出しMHをネスト
インスタンス変数のインライン化
$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)
                                           guardWithTest分岐
 @ 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)

                                 bindしておいたクラスとの比較

          インスタンス変数テーブルの参照
4. インスタンス変数(続き)

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

&guardWithTest(&,&,&):クラスが前回と違えば破棄
   &test(self):クラスに変更がないかテスト
   &filterRetval(&nullToNil):戻り値変換の合成
      &insert(obj = self, index = 2):引数挿入
         &getVariable(ctx,obj,index):ターゲット
   &fallback:新たなメソッド呼び出しMHをネスト
4. インスタンス変数(ネストの具体例)
&guardWithTest:クラスが前回と違えば破棄
  &test(self):クラスはFooか?
  &filterRetval:戻り値がnullならnilに変換
     &insert:テーブル序数として2を挿入
        &getVariable(index):ターゲット
  &fallback:新たなメソッド呼び出しMHをネスト
4. インスタンス変数(ネストの具体例)
&guardWithTest:クラスが前回と違えば破棄
  &test(self):クラスはBarか?
  &filterRetval:戻り値がnullならnilに変換
     &insert:テーブル序数として3を挿入
        &getVariable(index):ターゲット
  &guardWithTest:クラスが前回と違えば破棄
    &test(self):クラスはFooか?
    &filterRetval:戻り値がnullならnilに変換
       &insert:テーブル序数として2を挿入
          &getVariable(index):ターゲット
    &fallback:新たなメソッド呼び出しMHをネスト
def process(router)
                       router.say_hello("Ruby")
5. メソッド呼び出し          end


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

 呼び出しの型:
 (ctx;self)Object
 (ctx;self;arg1)Object
 (ctx;self;arg1;arg2)Object
 (ctx;self;arg1;arg2;arg3)Object
 (ctx;self;arg[])Object
5. メソッド呼び出し(続き)

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

&SwitchPoint:そのクラスでメソッドが定義されたら破棄
   &guardWithTest:レシーバークラスが違えば破棄
      &drop(ctx, self):引数を二つ落とす
         &test(self):クラスは同じか
      &target(ctx, self, arg1):ターゲット
      &fallback:新たなメソッド呼び出しMHをネスト
         ...
   &fallback:同じMHを再構築(→ターゲット更新)
メソッド呼び出しのインライン化
$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のチェック

$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)
                     どちらもOKならリンク済みMHを呼び出し
fib(n - 2) + fib(n - 1)
6. 算術演算呼び出し            display if (x >= 5)
                       elapsed = msec * 1000.0

適用先: 右辺が整数、小数の算術演算
● 右辺のunboxをショートカットする

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

 &guardWithTest:左辺がFixnumならショートカット
    &drop(ctx):引数を1つ落とす
       &test(self):左辺は整数か
    &fixnumMinusOne(ctx, self):ターゲット
    &invoke(ctx, self, value):直接呼び出し
算術演算呼び出しのインライン化
$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)    左辺はFixnumか?
 @ 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)


                            -1専用メソッドを呼び出す
#jt12_s204


JRuby + InvokeDynamic
       性能評価
    InvokeDynamic機能の活用
マイクロベンチマーク

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




     その他リテラル      インスタンス変数         算術演算
文字列リテラル      擬似定数       メソッド呼び出し          全て
二分木ベンチマーク

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




       AVL木(再帰)               赤黒木(ループ)
#jt12_s204
Java SE 7 InvokeDynamic
動的型付け言語にとっては革命的
複雑だが、言語処理系が自身で最適化するより楽
→ 以後、最適化はJVMに任せる方向へ

InvokeDynamic機能の適用対象候補
   ● プロファイラ・デバッガ
   ● 関数合成によるロジック再利用
   ● etc.

Java SE 7 InvokeDynamic in JRuby

  • 1.
    #jt12_s204 Java SE 7InvokeDynamic in JRuby 日本JRubyユーザ会 中村浩士 @nahi nahi@ruby-lang.org https://github.com/nahi http://slidesha.re/JavaOneJpInvokeDynamic
  • 2.
    自己紹介 ネットワークセキュリティ関連のシステム開発 C/C++ (18年)、Java(13年)、Ruby (13年) 余暇のOSS開発 CRuby (8年) とJRuby (2年) のコミッタ soap4r、httpclient他の開発
  • 3.
    Java SE 7InvokeDynamicとは Java SE 7に追加された新機能 変数に型のない動的型付け言語の性能向上支援 ● Java仮想マシン(JVM)のバイトコードに invokedynamic命令を追加 ● java.lang.invoke.*に関連APIを追加
  • 4.
    JRubyとは - http://jruby.org/ 最新リリース版は1.6.7 InvokeDynamic対応は1.7から (来月末のJRubyConfでPreviewリリース) JVM上で動作するRuby(動的型付け言語) Open Source (CPL, GPL, LGPL) 開発開始から10年
  • 5.
  • 6.
    Agenda 前半: InvokeDynamic機能解説 ●Java言語用メソッド呼び出し ● 動的型付け言語のメソッド呼び出し ● invokedynamic命令と関連API 後半: JRubyにおけるInvokeDynamicの活用 ● 利用パターン ● 性能評価
  • 7.
  • 8.
  • 9.
    JIT最適化の例: インライン化 doubleaddAllSqrts(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); } }
  • 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)
  • 11.
    JIT最適化の例 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) addSqrtをコンパイル 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 Math.sqrt呼び出しと加算を Target::addSqrt (8 bytes) inline (hot) @ 3 インライン化 java.lang.Math::sqrt (5 bytes) (intrinsic) @ 1 java.lang.Object::<init> (1 bytes) inline (hot)
  • 12.
    double addAllSqrts(int max){ double accum = 0; for (int i = 0; i < max; i++) { accum = addSqrt(accum, i); }} public static void main(String[] args) { % java -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions 0; i < 100000; ++i) Target for (int i = -XX:+PrintInlining { (new Target()).addAllSqrts(10); }} 66 1 Target::addAllSqrts (27 bytes) 67 2 Target::addSqrt (8 bytes) @mainをコンパイル 3 forの中にあるaddAllSqrtsおよび 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)
  • 13.
    double addAllSqrts(int max){ double accum = 0; for (int i = 0; i < max; i++) { accum = addSqrt(accum, i); }} OSR: On-stack public static void main(String[] args) { % java -XX:+PrintCompilation replacement -XX:+UnlockDiagnosticVMOptions 0; i < 100000; ++i) Target for (int i = -XX:+PrintInlining { (new Target()).addAllSqrts(10); }} 66 1 Target::addAllSqrts (27 bytes) 67 2 Target::addSqrt (8 bytes) @mainをコンパイル 3 forの中にあるaddAllSqrtsおよび 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)
  • 14.
  • 15.
  • 16.
    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); } }
  • 17.
    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); } }
  • 18.
    JVMでのメソッド呼び出し <Command> void processOptions(java.lang.String[]); process(opt.concat("?!")); 20: aload_0 // thisであるCommandをスタックに入れる 21: aload 5 23: ldc #2 25: invokevirtual #3 28: invokevirtual #4
  • 19.
    JVMでのメソッド呼び出し "yes" void processOptions(java.lang.String[]); <Command> process(opt.concat("?!")); 20: aload_0 // thisであるCommandをスタックに入れる 21: aload 5 // forループ引数optから"yes"を入れる 23: ldc #2 25: invokevirtual #3 28: invokevirtual #4
  • 20.
    JVMでのメソッド呼び出し "?!" void processOptions(java.lang.String[]); "yes" process(opt.concat("?!")); <Command> 20: aload_0 // thisであるCommandをスタックに入れる 21: aload 5 // forループ引数optから"yes"を入れる 23: ldc #2 // 定数"?!"を入れる 25: invokevirtual #3 28: invokevirtual #4
  • 21.
    JVMでのメソッド呼び出し "?!" void processOptions(java.lang.String[]); "yes" process(opt.concat("?!")); <Command> 20: aload_0 // thisであるCommandをスタックに入れる 21: aload 5 // forループ引数optから"yes"を入れる 23: ldc #2 // 定数"?!"を入れる 25: invokevirtual #3 // String.concat // スタックからループ引数と"?!"を取り出し // 引数としてconcatを呼んで... 28: invokevirtual #4
  • 22.
    JVMでのメソッド呼び出し "yes?!" void processOptions(java.lang.String[]); <Command> process(opt.concat("?!")); 20: aload_0 // thisであるCommandをスタックに入れる 21: aload 5 // forループ引数optから"yes"を入れる 23: ldc #2 // 定数"?!"を入れる 25: invokevirtual #3 // String.concat // スタックからループ引数と"?!"を取り出し // 引数としてconcatを呼んで // 戻り値をスタックに積む 28: invokevirtual #4
  • 23.
    JVMでのメソッド呼び出し "yes?!" void processOptions(java.lang.String[]); <Command> process(opt.concat("?!")); 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を呼ぶ
  • 24.
  • 25.
    Call Siteに必要な情報 コンパイル時: 呼び出し先の参照情報 メソッドが属するクラス: String メソッド名: "concat" メソッド型(引数と戻り値の型): (String;String)String [本資料でのメソッド型の表記方法] Objectとlongの2引数、Objectが戻り値 → (Object;long)Object
  • 26.
    Call Siteに必要な情報 コンパイル時: 呼び出し先の参照情報 メソッドが属するクラス: String メソッド名: "concat" メソッド型(引数と戻り値の型): (String;String)String 実行時 呼び出し先メソッドの実体: String#concat レシーバー / 引数オブジェクト: "yes" / "?!" 戻り値オブジェクト / 発生例外: "yes?!"
  • 27.
    Java言語用メソッド呼び出し命令 参照先メソッドを決定する4種のバイトコード invokestatic staticメソッドを直接リンク invokespecial private/super/コンストラクタ invokevirtual インスタンスメソッド検索 (virtual解決) invokeinterface インターフェースメソッド検索 (interface解決)
  • 28.
    invokevirtualのメソッド呼び出し コンパイル時: JComponent メソッドが属するクラス: +paint() JTextComponent メソッド名: "getText" JTextComponent +paint() メソッド型: ()Void +getText() 実行時: JTextField JTextArea 呼び出し先メソッドの実体: +paint() +paint() JTextField#getText +getText() +getText() +getColumns() +getRows() +getColumns()
  • 29.
    invokevirtualのメソッド呼び出し コンパイル時: JComponent メソッドが属するクラス: +paint() JTextComponent メソッド名: "getText" JTextComponent +paint() メソッド型: ()Void +getText() 実行時: JTextField JTextArea 呼び出し先メソッドの実体: +paint() +paint() JTextField#getText +getText() +getText() +getColumns() +getRows() +getColumns()
  • 30.
  • 31.
  • 32.
    動的型付け言語のメソッド呼び出し def process_options(options) for opt in options process(opt.concat("?!")) end end mock = Object.new def mock.concat(arg) "tested!" end options = ["yes", "no", mock] process_options(options)
  • 33.
  • 34.
    例: Java SE6用のJRuby実装 JRuby独自のCall Site (呼び出し先の参照情報を格納) 参照先メソッドの検索も独自実装
  • 35.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "process"CallSite // "process"呼び出し用のCallSiteをスタックに入れる aload_0 invokevirtual main.getCallSite2; aload 9 aload_0 invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
  • 36.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "concat"CallSite // "process"呼び出し用のCallSiteをスタックに入れる "process"CallSite aload_0 invokevirtual main.getCallSite2; // "concat"呼び出し用のCallSiteをスタックに入れる aload 9 aload_0 invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
  • 37.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "yes" // "process"呼び出し用のCallSiteをスタックに入れる "concat"CallSite aload_0 invokevirtual main.getCallSite2; "process"CallSite // "concat"呼び出し用のCallSiteをスタックに入れる aload 9 // forループ引数optから"yes"を入れる aload_0 invokevirtual main.getString0; invokevirtual CallSite.call; invokevirtual CallSite.call;
  • 38.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "?!" // "process"呼び出し用のCallSiteをスタックに入れる "yes" aload_0 invokevirtual main.getCallSite2; "concat"CallSite // "concat"呼び出し用のCallSiteをスタックに入れる "process"CallSite aload 9 // forループ引数optから"yes"を入れる aload_0 invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call; invokevirtual CallSite.call;
  • 39.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "?!" // "process"呼び出し用のCallSiteをスタックに入れる "yes" aload_0 invokevirtual main.getCallSite2; "concat"CallSite // "concat"呼び出し用のCallSiteをスタックに入れる "process"CallSite aload 9 // forループ引数optから"yes"を入れる aload_0 invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call; // スタックのCallSite情報を元に動的メソッド呼び出し(concat) invokevirtual CallSite.call;
  • 40.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "yes?!" // "process"呼び出し用のCallSiteをスタックに入れる "process"CallSite aload_0 invokevirtual main.getCallSite2; // "concat"呼び出し用のCallSiteをスタックに入れる aload 9 // forループ引数optから"yes"を入れる aload_0 invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call; // スタックのCallSite情報を元に動的メソッド呼び出し(concat) invokevirtual CallSite.call;
  • 41.
    Java SE 6用のJRuby生成バイトコード process(opt.concat("?!")) aload_0 invokevirtual main.getCallSite1; "yes?!" // "process"呼び出し用のCallSiteをスタックに入れる "process"CallSite aload_0 invokevirtual main.getCallSite2; // "concat"呼び出し用のCallSiteをスタックに入れる aload 9 // forループ引数optから"yes"を入れる aload_0 invokevirtual main.getString0; // 引数の"?!" invokevirtual CallSite.call; // スタックのCallSite情報を元に動的メソッド呼び出し(concat) invokevirtual CallSite.call; // 同じく動的メソッド呼び出し(process)
  • 42.
    Java SE 6でのJRubyメソッド呼び出し def target(opt) process(opt.concat("?!")) end 引数の数、順序の調整 デフォルト引数の補完 RubyString# CallSite Invoker ... concat invokevirtual getCallSite2 最終的に aload 9 ここを呼ぶ aload_0 呼び出しメソッド検索 invokevirtual getString0 メソッドキャッシュ invokevirtual CallSite.call キャッシュミス判定
  • 43.
    JRuby独自の最適化 素朴な実装のままでは遅いので... ● メソッドキャッシュ /無効化 ● JITコンパイル ○ バイトコード動的生成→読み込み ○ 脱最適化 より詳しく: http://bit.ly/JRubyHackingGuide
  • 44.
  • 45.
    Java SE 6でのJRubyメソッド呼び出し 引数の数、順序の調整 デフォルト引数の補完 ... RubyString# invokevirtual getCallSite2 CallSite Invoker concat aload 9 aload_0 最終的に invokevirtual getString0 ここを呼ぶ invokevirtual CallSite.call 呼び出しメソッド検索 メソッドキャッシュ キャッシュミス判定
  • 46.
    Java SE 7でのJRubyメソッド呼び出し 呼び出しメソッド検索 メソッドキャッシュ 引数の数、順序の調整 キャッシュミス判定 デフォルト引数の補完 MethodHandle bootstrap API RubyString# CallSite Invoker concat ... ic edy nam 最終的に nvok aload 9 invokedynamic getString i ここを呼ぶ invokedynamic concat
  • 47.
    InvokeDynamic用生成バイトコード process(opt.concat("?!")) aload_2 // thisであるCommandをスタックに入れる aload 9 // forループ引数optから"yes"を入れる invokedynamic getString [...] // 引数の"?!"を取り出す invokedynamic concat (IRubyObject;IRubyObject)IRubyObject [...] // "concat"メソッドを動的呼び出し invokedynamic process (IRubyObject;IRubyObject)IRubyObject [...] // "process"メソッドを動的呼び出し
  • 48.
    生成されるバイトコードの違い 独自のCall Siteはなくinvokedynamic命令で直接呼び出す concatの呼び出し情報は以下 メソッド名: "concat" メソッド型: (IRubyObject;IRubyObject)IRubyObject "concat"という名前のメソッドを、2つのRubyオブジェクト (先頭レシーバ、引数1つ)と共に呼び出し戻り値を得る ...呼び出し先はどこ?
  • 49.
  • 50.
    bootstrapメソッド invokedynamic命令をよく見ると... invokedynamic concat(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite) ] invokedynamic process(IRubyObject;IRubyObject)IRubyObject [ invocationBootstrap((Lookup;String;MethodType)CallSite) ] ...bootstrapと呼ぶ初期化メソッドが登録されている
  • 51.
    bootstrapメソッド (Lookup;String;MethodType)CallSite 初回実行時のみ呼ばれるユーザ定義メソッド Lookup: メソッド検索用オブジェクト String: メソッド名 ("concat") MethodType: スタック上の引数オブジェクト型 CallSite: 検索した呼び出し先メソッドの参照 (MethodHandle)を格納
  • 52.
    Java SE 7からのメソッド呼び出し命令 invokestatic staticメソッドを直接リンク invokespecial private/super/コンストラクタ invokevirtual インスタンスメソッド検索 (virtual解決) invokeinterface インターフェースメソッド検索 (interface解決) invokedynamic 動的MethodHandle検索 (bootstrapによる解決)
  • 53.
    Java SE 7InvokeDynamicとは何か コンセプト 道具 Call Siteと呼出先の動的なリンク invokedynamic, CallSite リンク先検索ロジックをプログラム可能 bootstrap メソッド参照 MethodHandle 型の安全かつ自動的な変換 MethodType 実行時の呼び出し先メソッド分岐 MethodHandle合成 動的型付け言語で、Javaの呼び出しと同じ最適化
  • 54.
    MethodHandle(MH)操作API Lookup#* クラス名と名前指定でMHを生成 MethodHandle#bindTo 第1引数のレシーバを固定 MethodHandles#* MHを合成して新たなMHを生成 insertArguments 引数を部分適用したMHを生成 guardWithTest test, then, else用の3MHを合成 して実行時に分岐するMH SwitchPoint#guardWithTest より最適化された true/false分岐のMH生成 SwitchPoint.invalidateAll sptの無効化
  • 55.
    def fib(n) if n < 2 MH操作の例 n else fib(n - 2) + fib(n - 1) end "n - 1"のリンク先は? end 最初に呼ばれたXInteger#minusにリンク ただし毎回nの型 == XIntegerのチェックは必要 4つのMHを合成してCallSiteに設定 引数nの型がXIntegerかテストするメソッドのMH 引数1を部分適用したXInteger#minus(1)のMH XInteger#minus(a)を実装したJavaメソッドのMH 呼出先MHを検索してCallSiteに再設定するメソッドのMH
  • 56.
    引数nの型がXIntegerかテストするメソッドのMH 引数1を部分適用したXInteger#minus(1)のMH XInteger#minus(a)を実装したJavaメソッドのMH 呼出先MHを検索してCallSiteに再設定するメソッドのMH MethodHandle test, minus, fallback, all; minus = lookup.findVirtual(XInteger.class, "minus", MethodType.methodType(XObject.class, long.class)); minus = MethodHandles.insertArguments(minus, 1, 1); 1番目の引数に 1Lを部分適用
  • 57.
    引数nの型がXIntegerかテストするメソッドのMH 引数1を部分適用したXInteger#minus(1)のMH XInteger#minus(a)を実装したJavaメソッドのMH 呼出先MHを検索してCallSiteに再設定するメソッドの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);
  • 58.
    引数nの型がXIntegerかテストするメソッドのMH 引数1を部分適用したXInteger#minus(1)のMH XInteger#minus(a)を実装したJavaメソッドのMH 呼出先MHを検索してCallSiteに再設定するメソッドの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);
  • 59.
  • 60.
    #jt12_s204 JRubyにおける InvokeDynamic 利用パターン InvokeDynamic機能の活用
  • 61.
    JRuby InvokeDynamic利用パターン 1. 文字列リテラル 2.その他リテラル 3. 擬似定数 4. インスタンス変数 5. メソッド呼び出し 6. 算術演算呼び出し
  • 62.
    1. 文字列リテラル message = "Hello" message << name << "!" 適用先: リテラル文字列 ● "Hello"はbootstrapに渡すようバイトコード生成 ● (ctx)Object を (ctx;str)Object にリンクするため、str引数 を挿入する関数を合成 ※ThreadContextはThreadなど実行環境情報を格納したオブジェクト 呼び出しの型: (ThreadContext ctx)Object &insert(str = "Hello"):引数を1つ挿入 &newString(ctx, str):ターゲット 合成
  • 63.
    文字列リテラル参照のインライン化 def target "Hello" end idx = 0 while idx < 50000 target idx += 1 end
  • 64.
    文字列リテラル参照のインライン化 $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.*
  • 65.
    文字列引数の挿入操作 リンクしたターゲット $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 この辺はJRubyの内部実装 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
  • 66.
    文字列引数の挿入操作 リンクしたターゲット $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) &insert(str = "Hello"):引数を1つ挿入 &newString(ctx, str):ターゲット
  • 67.
    times = 10000 2.その他リテラル matcher = /[A-Z][a-z]*/ 適用先: 文字列以外の不変リテラル ● 定数値はbootstrapに渡すようバイトコード生成 ● 定数を返すMHを生成 ● (ctx)Object から ()Object にリンクするため、ctx引数を削 る関数を合成 呼び出しの型: (ThreadContext ctx)Object &drop(ctx):引数を1つ削る &constant[10000]:常に10000を返すMH
  • 68.
    その他リテラル参照のインライン化 引数の削除操作 $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にリンク
  • 69.
    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) Java SE 7でのインライン化結果 $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 6でのインライン化結果
  • 70.
    DEFAULT = Container.new.freeze 3.擬似定数 comtainer = DEFAULT DEFAULT = nil 適用先: Rubyの定数参照 ● Rubyの定数は変更可能なため、「上書きされることの少な い変数」として扱う ● 上書き検出にSwitchPointを使う 呼び出しの型: (ThreadContext ctx)Object &SwitchPoint(&,&):任意の定数が定義されたら破棄 &drop(ctx):引数を1つ削る &constant[obj]:現在の値を定数として返すMH &fallback(ctx):同じMHを再構築(→値を再取得)
  • 71.
    擬似定数参照のインライン化 $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)
  • 72.
    SwitchPointの内部 SwitchPoint分岐 $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の無効化チェック 通常は定数を返す 無効化されていたら再リンクメソッドへ
  • 73.
    module Cache 4. インスタンス変数 def cache(value) @cache = value end 適用先: インスタンス変数アクセス end ● 呼び出し側selfの変数テーブルを参照 class Foo include Cache ● 変数テーブルはクラスにより異なる end class Bar ● モジュールが他のクラスに include Other includeされている場合、クラスにより include Cache end "@cache"のテーブル内位置が異なる ● クラスの切り替え判定にguardWithTestを使う
  • 74.
    4. インスタンス変数(続き) 呼び出しの型: (ctx;Objectself)Object &guardWithTest(&,&,&):クラスが前回と違えば破棄 &test(self):クラスに変更がないかテスト &filterRetval(&nullToNil):戻り値変換の合成 &insert(obj = self, index = 2):引数挿入 &getVariable(ctx,obj,index):ターゲット &fallback:新たなメソッド呼び出しMHをネスト
  • 75.
    インスタンス変数のインライン化 $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)
  • 76.
    guardWithTestによる分岐 $file::method__1$RUBY$target (7 bytes) guardWithTest分岐 @ 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) bindしておいたクラスとの比較 インスタンス変数テーブルの参照
  • 77.
    4. インスタンス変数(続き) 呼び出しの型: (ctx;Objectself)Object &guardWithTest(&,&,&):クラスが前回と違えば破棄 &test(self):クラスに変更がないかテスト &filterRetval(&nullToNil):戻り値変換の合成 &insert(obj = self, index = 2):引数挿入 &getVariable(ctx,obj,index):ターゲット &fallback:新たなメソッド呼び出しMHをネスト
  • 78.
    4. インスタンス変数(ネストの具体例) &guardWithTest:クラスが前回と違えば破棄 &test(self):クラスはFooか? &filterRetval:戻り値がnullならnilに変換 &insert:テーブル序数として2を挿入 &getVariable(index):ターゲット &fallback:新たなメソッド呼び出しMHをネスト
  • 79.
    4. インスタンス変数(ネストの具体例) &guardWithTest:クラスが前回と違えば破棄 &test(self):クラスはBarか? &filterRetval:戻り値がnullならnilに変換 &insert:テーブル序数として3を挿入 &getVariable(index):ターゲット &guardWithTest:クラスが前回と違えば破棄 &test(self):クラスはFooか? &filterRetval:戻り値がnullならnilに変換 &insert:テーブル序数として2を挿入 &getVariable(index):ターゲット &fallback:新たなメソッド呼び出しMHをネスト
  • 80.
    def process(router) router.say_hello("Ruby") 5. メソッド呼び出し end 適用先: 任意のメソッド呼び出し ● レシーバーのクラスに応じ呼び出し先が変わる ● レシーバークラスのメソッドが上書きされる可能性がある ● 引数の個数に応じて5つのタイプ 呼び出しの型: (ctx;self)Object (ctx;self;arg1)Object (ctx;self;arg1;arg2)Object (ctx;self;arg1;arg2;arg3)Object (ctx;self;arg[])Object
  • 81.
    5. メソッド呼び出し(続き) 呼び出しの型: (ctx;self;arg1)Object &SwitchPoint:そのクラスでメソッドが定義されたら破棄 &guardWithTest:レシーバークラスが違えば破棄 &drop(ctx, self):引数を二つ落とす &test(self):クラスは同じか &target(ctx, self, arg1):ターゲット &fallback:新たなメソッド呼び出しMHをネスト ... &fallback:同じMHを再構築(→ターゲット更新)
  • 82.
    メソッド呼び出しのインライン化 $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)
  • 83.
    メソッド再定義用に 分岐のネスト SwitchPointのチェック $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) どちらもOKならリンク済みMHを呼び出し
  • 84.
    fib(n - 2)+ fib(n - 1) 6. 算術演算呼び出し display if (x >= 5) elapsed = msec * 1000.0 適用先: 右辺が整数、小数の算術演算 ● 右辺のunboxをショートカットする 呼び出しの型: (ctx;self)Object &guardWithTest:左辺がFixnumならショートカット &drop(ctx):引数を1つ落とす &test(self):左辺は整数か &fixnumMinusOne(ctx, self):ターゲット &invoke(ctx, self, value):直接呼び出し
  • 85.
    算術演算呼び出しのインライン化 $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)
  • 86.
    算術演算呼び出しのインライン化 $file::method__0$RUBY$target (14 bytes) 左辺はFixnumか? @ 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) -1専用メソッドを呼び出す
  • 87.
    #jt12_s204 JRuby + InvokeDynamic 性能評価 InvokeDynamic機能の活用
  • 88.
    マイクロベンチマーク ※高さはJava 7非indyを1としたときの速度比率。高いほうが速い。 ※個々のパターンの処理のみを繰り返しループして測定。 その他リテラル インスタンス変数 算術演算 文字列リテラル 擬似定数 メソッド呼び出し 全て
  • 89.
  • 90.
    #jt12_s204 Java SE 7InvokeDynamic 動的型付け言語にとっては革命的 複雑だが、言語処理系が自身で最適化するより楽 → 以後、最適化はJVMに任せる方向へ InvokeDynamic機能の適用対象候補 ● プロファイラ・デバッガ ● 関数合成によるロジック再利用 ● etc.