uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

周知: JGGUG名物・ライトじゃないLT大会 - JGGUG G*ワークショップZ Sep 2013 #jggug

http://jggug.doorkeeper.jp/events/5745

G*ワークショップZは日本Grails/Groovyユーザーグループ(JGGUG)の定例イベントです。 Groovy、 Grails、 Griffon、 GradleといったG*技術をテーマに、ハンズオンやコードリーディング、読書会など参加型の内容で毎月第3金曜日に開催しています。

新生G*ワークショップ"Z" 第7弾は…

JGGUG名物・ライトじゃないLT大会!

今回、Engine Yardさまにスペースをお借りして、ビールやピザを片手にLT大会となります!前回までの会場とは違い、最寄り駅が恵比寿ですのでご注意を!

わたしもLTかライトじゃないLT(GT)をしたいと思います

indyでBrainfuckを実装してみよう!

f:id:uehaj:20130823134705p:plain

唐突ですが、indy*1を使用して、Brainfuck言語処理系を実装してみます。Brainfuckはいわゆる一つの奇妙なプログラミング言語ですが、処理系の実装が容易であることを重視して設計されたチューリング完全な言語であり、試しに実装してみるのには良いでしょう。

とはいえ、残念ながらBrainfuck動的言語ではないので、あんまりindyが役にたちません。

そこで、一捻りとして、"++--"といった命令列を文字列として保持し、Bootstrapメソッドの実行時に「メソッド呼び出しの列を表現するMH」として生成してみます。"+"がp()、"-"がm()というメソッド呼び出しに対応するとすると、"++--"というオプション引数を受けとったならば、「p(), p(), m(), m(),」という一連の呼び出しを結合したMHをMethodHandles.filterReturnValueで作成し、CallSiteにして返すようなBootstrapメソッドを実装します。これによってクラスファイルのサイズが減少することを期待します。

以下がBrainfuckコンパイラのコード(compile.groovy)です。

// ネストしたループにおけるラベル名を管理するためのデータとコード。
def list = [].withDefault{0}
def nestLevel = 0
def level = { it << list[0..nestLevel-1].join('_') }.asWritable()
def increaseLevel = { list[nestLevel++]++; level(it) }.asWritable()
def decreaseLevel = { level(it); nestLevel-- }.asWritable()

// Brainfuck命令をJVMバイトコードにコンバートする
def genCode = {
    new File(args[0]).text.replaceAll(/[^\+\-\<\>\.\,\[\]]/, '').eachMatch(/[^\[\]]+|[\[\]]/) { g0 ->
      if (g0 == '[') {
         it << """        _GOTO          tmp$increaseLevel
    lab$level:
"""
      }
      else if (g0 == ']') {
        it << """    tmp$level:
        getstatic     '.data','[B'
        getstatic     '.dp','I'
        baload        
        ifne          lab$decreaseLevel
"""
      }
      else {
         it << """        invokedynamic 'dummy', '()V', [H_INVOKESTATIC, 'Brainfuck', 'bootstrap', [CallSite, Lookup, String, MethodType, String]], '$g0'
"""
      }
    }
}.asWritable()

println """\
@GrabResolver(name="maven-repo", root="https://raw.github.com/uehaj/maven-repo/gh-pages/snapshot")
@Grab("groovyx.ast.bytecode:groovy-bytecode-ast:0.2.0-separate-asm")
import groovyx.ast.bytecode.Bytecode
import java.lang.invoke.*
import java.lang.invoke.MethodHandles.Lookup
import static groovyjarjarasm.asm.Opcodes.H_INVOKESTATIC

class Brainfuck {

    // Bootstrapメソッド
    public static CallSite bootstrap(Lookup lookup, String methodName, MethodType type, String instructions) {
        MethodHandle result = null
        instructions.tr('<>.','lgo').each { insn ->
            MethodHandle mh = lookup.findStatic(Brainfuck, insn, MethodType.methodType(void.class))
            result = (result == null) ? mh : MethodHandles.filterReturnValue(result, mh)
        }
        new ConstantCallSite(result)
    }
    
    // Brainfuck実行のためのデータ構造
    static int dp // データポインタ
    static byte[] data = new byte[30000] // メモリ
    
    // Brainfuck命令群
    static void '+'(){data[dp]++}
    static void '-'(){data[dp]--}
    static void l(){dp--} // メソッド名が'<'だとクラスファイルとして違法なメソッド名になるので妥協
    static void g(){dp++} // '>'
    static void o(){print((char)data[dp])} // '.'
    static void ','(){System.in.read(data, dp, 1)}

    @Bytecode
    static void main(String[] args) throws Exception {
        // Brainfuckからコンバートされたコード
$genCode
        return
    }
}"""

Bytecode DSLを使っています。
このコードは、Brainfuck言語のソースファイルを引数として起動すると、Bytecode DSLを用いたアセンブラコードを含むGroovyコードを標準出力に出力します。
以下は実行例です。

% cat hello.bf 
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>++++++++[<++++>-]<+.[-]++++++++++.

% groovy compile.groovy hello.bf > a.groovy
% groovy -Dgroovy.target.bytecode=1.7 a.groovy
Hello World!


上でa.groovyにはこんなコードが生成されています(抜粋)。全体はこちら

class Brainfuck {

    // Bootstrapメソッド
    public static CallSite bootstrap(Lookup lookup, String methodName, MethodType type, String instructions) {
        MethodHandle result = null
        instructions.tr('<>.','lgo').each { insn ->
            MethodHandle mh = lookup.findStatic(Brainfuck, insn, MethodType.methodType(void.class))
            result = (result == null) ? mh : MethodHandles.filterReturnValue(result, mh)
        }
        new ConstantCallSite(result)
    }
    
    // Brainfuck実行のためのデータ構造
    static int dp // データポインタ
    static byte[] data = new byte[30000] // メモリ
    
    // Brainfuck命令群
    static void '+'(){data[dp]++}
    static void '-'(){data[dp]--}
    static void l(){dp--} // <
    static void g(){dp++} // >
    static void o(){print((char)data[dp])} // .
    static void ','(){System.in.read(data, dp, 1)}

    @Bytecode
    static void main(String[] args) throws Exception {
        // Brainfuckからコンバートされたコード
        invokedynamic 'dummy', '()V', [H_INVOKESTATIC, 'Brainfuck', 'bootstrap', [CallSite, Lookup, String, MethodType, String]], '>+++++++++'
        _GOTO          tmp1
    lab1:
        invokedynamic 'dummy', '()V', [H_INVOKESTATIC, 'Brainfuck', 'bootstrap', [CallSite, Lookup, String, MethodType, String]], '<++++++++>-'
    tmp1:
        getstatic     '.data','[B'
        getstatic     '.dp','I'
        baload        
        ifne          lab1
        invokedynamic 'dummy', '()V', [H_INVOKESTATIC, 'Brainfuck', 'bootstrap', [CallSite, Lookup, String, MethodType, String]], '<.>+++++++'
        _GOTO          tmp2
    lab2:
        invokedynamic 'dummy', '()V', [H_INVOKESTATIC, 'Brainfuck', 'bootstrap', [CallSite, Lookup, String, MethodType, String]], '<++++>-'
   :

実行にはJava 7が必要です。indyなので。
詳しくはこちらこちら

では!!

*1:Java VM上での動的言語の実行を効率化することを目的とした一連の機能拡張(JSR 292: Supporting Dynamically Typed Languages on the Java Platform)が導入されました。この機能拡張で追加された新規バイトコード命令「invokedynamic」にちなみ、この機能を愛称としてindyと呼びます。

夏サミ2013「中堅SIerにおいて、
先進的なフルスタックフレームワークGrailsを全社的に普及推進してみた。」

デブサミSummer(夏サミ)にて発表します。

物語1:エンタープライズGrails
中堅SIerにおいて、
先進的なフルスタックフレームワークGrailsを全社的に普及推進してみた。

残席わずかとのことなので、ご興味のある方はお早めに是非どうぞ。
http://event.shoeisha.jp/detail/46/session/88/

HATEOASって何だ?

Grails 2.3のRest機能のドキュメントを読んでいたら、拡張の一つとして「8.1.7 Hypermedia as the Engine of Application State」というのが書いてあって、調べると面白かったので、この資料(REST: From GET to HATEOAS)を読んだだけでの、私の理解する限りのメモを記しておきます。

一言でいうと、HATEOASとは、Restfulパターンを拡張するアーキテクチャパターンで、Restful原則に対する追加的な制約。どういうものかというと、HTMLアプリの画面遷移を抽象化した、状態遷移を表現するRestful API(=Restful WebアプリのWebインターフェース)を設計するための具体的な方法論になってる。

もちろんGrailsに特化したものではなく、Restと同じレベルのWebアプリケーション一般概念でありRestを拡張する。すでにサポートするライブラリがいくつかあって、Spring FrameworkでもHATEOAS拡張をしている。Grailsのはそれをたぶんラッピングしている。

具体的には、

  • Restというのは言っても難しいよね
    • 単に何かを読み書きする、というアプリならいいかもしれない。GETとPUTとPOSTとDELETEでそれを読み書きする。
  • でも、一般にWebアプリケーションのサーバサイドというのは、そんなに単純じゃない。
  • 例えばeBayのようなオークションアプリをRestfulアプリとして定義するとしよう。こんな感じ。
    • 品物をウォッチリストに入れる
    • 売り主の評価をチェック
    • 売主の詳細を見る
    • 入札する。
  • これらを、正しい順序で呼び出す必要がある。ステートとそれぞれの選択肢、それらを合わせたフロー構造がありルールがある。これらはどこで定義されるべきか?
    • Restfulにせよ何にせよ、本来「Webアプリケーション」が定めるものの一部であるはず。
    • HTMLアプリとの比較で言うと、HTMLアプリの画面遷移は、利用者から見たアプリケーションの状態遷移を表現していて、リンクはそれぞれの状態で「何ができるか」を提示していた。
    • 同じことをRestでしようとしたとき、それらのステートとフローの構造をクライアントJavaScriptで記述されるアプリ中にハードコーディングで埋没させて定義して良いのだろうか?
    • だってそうだと、サーバサイドの独立性というものが無いよね。特定のクライアントと結合しないと動かないなら、Restサーバの利点が損耗する。
  • そこでHATEOASですよ
    • HATEOASでは、
      • まず、基本はRest
      • その上に、上の意味でのアプリの「状態」を表現するXMLないしJSON表現をアプリケーション固有のものとして定義
      • 個々の状態を表現するXML/JSONに、固有のMimeTypeを与える
      • その表現の中に、リンクが含まれている
      • リンクを辿ることが、アプリケーションの状態遷移である。
    • この場合、クライアントの役割は、HATEOASなAPIのブラウザになる。
    • 結果として状態遷移とフロー構造を外在化・明示化することになる
      • 結果として、Restful APIを介して、クライアントとサーバのより明確な構造的分離が達成できる

んで、来るべきGrails 2.3ではHATEOASなアプリを簡単に書けるようになるよ、と。
しかし、なんて読むんだろう。ハテオアス?

Web API: The Good Parts
Web API: The Good Parts
posted with amazlet at 16.05.19
水野 貴明
オライリージャパン
売り上げランキング: 38,557

G*ワークショップZ Jun 2013 - GR8Conf報告&ライブコーディング&GroovyServ/Improx

今週末は、G*Workshopです。
http://jggug.doorkeeper.jp/events/3873

ご興味のあるかたは是非どうぞご参加下さい。
今回も、前回に引き続きNTTSOFTの「新」品川オフィスです。
つまりビルが前々回までと異なります。
ご注意くだだい。

Java8におけるindyとLambdaの絶妙な関係、もしくはSAMタイプを継承する内部クラスの.classファイルはどこへ行ったの?

JJUG CCC 2013 Springのさくらばさんのセッションで「Java 8の怖い話」として、Lambda式をJVM上で実行するにあたり、

  • invokedynamicを使っている
  • Lambda式の仕様は、意味的にはSamタイプを継承する内部クラスのインスタンスのはずだが、lambda式を使っているJavaソースをコンパイルしても、内部クラスに対応する.classファイルが生成されない

という話を聞きました。Lambdaの実行に、内部的にバイトコードを生成して使っている、という話だったのですが、indy使ってるってのは、良く考えると解せないです。Lambdaになんらかの動的な側面があるんでしょうかね???

ということで、調べてみました。

[:contents]

結論

結論から書くと、OracleのJava8のJDKのJavacは、Lambda式の本体を、SAMタイプを継承する内部クラスのメソッドではなく、コンパイル対象のクラスに直接所属するメソッドとしてコンパイルします。また、Lambda式の使用は、invokedynamic命令にコンパイルされます。そして確かに、コンパイル時には、そのメソッドを持つSAMタイプを継承するクラスの.classファイルは生成されません。

そして、invokedynamic命令の初回実行時に、その命令に付随させたブートストラップメソッドの処理の中で、SAMタイプを継承するクラスを動的にオンメモリにバイト列として生成し、それをクラスローダにロードさせ、そのクラスのインスタンスをnewする処理が、invokedynamicの実際の処理に置き換わります*1。この生成されたクラスのSAMメソッドではおそらく、MethodHandle経由で先の(2013/6/13削除。こちらを見ると、MH経由ではない)Lambda本体に相当するメソッドを呼び出します。

ちなみに、「オンメモリにクラスを動的に生成する」という処理を、Java VMではObjectweb asmライブラリを使って行なっています。JVMの中でasmを使ってるとは知らなかった。

indyをLambda式の評価に使ってる理由の一つは、おそらく、lambda式の初回呼び出し時までSAMタイプを継承したクラスの生成およびロードのタイミングを遅延させるためです。この方式なら、例えLambda式が何100個記述されていようとも、そのLambda式が実際に呼び出されなければクラスは全く生成もロードもされないので、クラスロード処理やクラスファイルを読み込むためのファイルアクセス処理が節約でき、起動や初期化の時間が短縮できるのでしょう。また、内部クラスという別クラスではなく、実際にそのクラスにLambda本体のメソッドがあるので、内部クラスを使った場合に生じていた可視性の問題の回避も簡略化できたはずです。

賢いわー。

(2013/06/12 23:52追記)
桜庭さんより(!)Facebookにてコメント頂き、本件に関し、この資料「Lambda: A peek under the hood」が参考になるとのこと。おお!! ざっと見ですが、利点として、字面が同じlambdaは生成SAMクラスを共有できる、とかもあるようです。Type profile pollutionは意味がわかりませんでしたorz。ありがとうございました>桜庭さん

確認してみよう

例えば、Lambda式を含む、

public class Test {
	static void foo(Runnable r) {
		r.run();
	}
	public static void main(String[] args) {
		foo(()->{System.out.println("hello");});
	}
}

こんなJavaソースをまずはコンパイルして実行してみます。

$ javac Test.java
$ java Test
hello
$ ls -la
total 16
drwxr-xr-x   4 uehaj  staff   136  6 12 22:22 ./
drwxr-xr-x  41 uehaj  staff  1394  6 12 22:20 ../
-rw-r--r--   1 uehaj  staff  1095  6 12 22:22 Test.class
-rw-r--r--   1 uehaj  staff   182  6 12 22:21 Test.java

確かに、Test.class以外のクラスファイルは生成されてませんね。javapしてみましょう。

$ javap -p Test
Compiled from "Test.java"
public class Test {
  public Test();
  static void foo(java.lang.Runnable);
  public static void main(java.lang.String[]);
  private static void lambda$0();   // ←★
}

★のところの「lambda$0();」がLamda式のボディに対応するメソッドです。

メソッドの宣言だけではなく、メソッド本体のバイトコードとかコンスタントプール情報も見るために、javapに-c -p -vオプションをつけて実行してみます。以降、行番号は判りやすさのために付与しました。

$ javap -p -c -v Test
     1	Classfile /Users/uehaj/work/201305/java8_2/y/Test.class
     2	  Last modified 2013/06/12; size 1095 bytes
     3	  MD5 checksum d69a213c626bb412c306b8f3ea88cac4
     4	  Compiled from "Test.java"
     5	public class Test
     6	  SourceFile: "Test.java"
     7	  InnerClasses:
     8	       public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V
    15	  minor version: 0
    16	  major version: 52
    17	  flags: ACC_PUBLIC, ACC_SUPER
    18	Constant pool:
    19	   #1 = Methodref          #9.#21         //  java/lang/Object."<init>":()V
    20	   #2 = InterfaceMethodref #22.#23        //  java/lang/Runnable.run:()V
    21	   #3 = InvokeDynamic      #0:#29         //  #0:lambda$:()Ljava/lang/Runnable;
    22	   #4 = Methodref          #8.#30         //  Test.foo:(Ljava/lang/Runnable;)V
    23	   #5 = Fieldref           #31.#32        //  java/lang/System.out:Ljava/io/PrintStream;
    24	   #6 = String             #33            //  hello
    25	   #7 = Methodref          #34.#35        //  java/io/PrintStream.println:(Ljava/lang/String;)V
    26	   #8 = Class              #36            //  Test
    27	   #9 = Class              #37            //  java/lang/Object
    28	  #10 = Utf8               <init>
    29	  #11 = Utf8               ()V
    30	  #12 = Utf8               Code
    31	  #13 = Utf8               LineNumberTable
    32	  #14 = Utf8               foo
    33	  #15 = Utf8               (Ljava/lang/Runnable;)V
    34	  #16 = Utf8               main
    35	  #17 = Utf8               ([Ljava/lang/String;)V
    36	  #18 = Utf8               lambda$0
    37	  #19 = Utf8               SourceFile
    38	  #20 = Utf8               Test.java
    39	  #21 = NameAndType        #10:#11        //  "<init>":()V
    40	  #22 = Class              #38            //  java/lang/Runnable
    41	  #23 = NameAndType        #39:#11        //  run:()V
    42	  #24 = Utf8               BootstrapMethods
    43	  #25 = MethodHandle       #6:#40         //  invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    44	  #26 = MethodHandle       #9:#2          //  invokeinterface java/lang/Runnable.run:()V
    45	  #27 = MethodHandle       #6:#41         //  invokestatic Test.lambda$0:()V
    46	  #28 = MethodType         #11            //  ()V
    47	  #29 = NameAndType        #42:#43        //  lambda$:()Ljava/lang/Runnable;
    48	  #30 = NameAndType        #14:#15        //  foo:(Ljava/lang/Runnable;)V
    49	  #31 = Class              #44            //  java/lang/System
    50	  #32 = NameAndType        #45:#46        //  out:Ljava/io/PrintStream;
    51	  #33 = Utf8               hello
    52	  #34 = Class              #47            //  java/io/PrintStream
    53	  #35 = NameAndType        #48:#49        //  println:(Ljava/lang/String;)V
    54	  #36 = Utf8               Test
    55	  #37 = Utf8               java/lang/Object
    56	  #38 = Utf8               java/lang/Runnable
    57	  #39 = Utf8               run
    58	  #40 = Methodref          #50.#51        //  java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    59	  #41 = Methodref          #8.#52         //  Test.lambda$0:()V
    60	  #42 = Utf8               lambda$
    61	  #43 = Utf8               ()Ljava/lang/Runnable;
    62	  #44 = Utf8               java/lang/System
    63	  #45 = Utf8               out
    64	  #46 = Utf8               Ljava/io/PrintStream;
    65	  #47 = Utf8               java/io/PrintStream
    66	  #48 = Utf8               println
    67	  #49 = Utf8               (Ljava/lang/String;)V
    68	  #50 = Class              #53            //  java/lang/invoke/LambdaMetafactory
    69	  #51 = NameAndType        #54:#58        //  metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    70	  #52 = NameAndType        #18:#11        //  lambda$0:()V
    71	  #53 = Utf8               java/lang/invoke/LambdaMetafactory
    72	  #54 = Utf8               metaFactory
    73	  #55 = Class              #60            //  java/lang/invoke/MethodHandles$Lookup
    74	  #56 = Utf8               Lookup
    75	  #57 = Utf8               InnerClasses
    76	  #58 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    77	  #59 = Class              #61            //  java/lang/invoke/MethodHandles
    78	  #60 = Utf8               java/lang/invoke/MethodHandles$Lookup
    79	  #61 = Utf8               java/lang/invoke/MethodHandles
    80	{
    81	  public Test();
    82	    flags: ACC_PUBLIC
    83	    Code:
    84	      stack=1, locals=1, args_size=1
    85	         0: aload_0       
    86	         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
    87	         4: return        
    88	      LineNumberTable:
    89	        line 3: 0
    90	
    91	  static void foo(java.lang.Runnable);
    92	    flags: ACC_STATIC
    93	    Code:
    94	      stack=1, locals=1, args_size=1
    95	         0: aload_0       
    96	         1: invokeinterface #2,  1            // InterfaceMethod java/lang/Runnable.run:()V
    97	         6: return        
    98	      LineNumberTable:
    99	        line 5: 0
   100	        line 6: 6
   101	
   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8
   112	
   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

長いっ!

なので以降、分解して見ていきます。

Lambda式はどうなったか

Lambdaの本体に対応しそうなのは、

   113	  private static void lambda$0();
   114	    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
   115	    Code:
   116	      stack=2, locals=0, args_size=0
   117	         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
   118	         3: ldc           #6                  // String hello
   119	         5: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
   120	         8: return        
   121	      LineNumberTable:
   122	        line 8: 0
   123	}

の部分であり、確かにlambda$0メソッドが、

()->{ System.out.println("hello"); }

の「System.out.println("hello")」近辺に対応していそうです。

invokedynamicの動き

まず、mainメソッドに対応する以下の部分を見ると、

   102	  public static void main(java.lang.String[]);
   103	    flags: ACC_PUBLIC, ACC_STATIC
   104	    Code:
   105	      stack=1, locals=1, args_size=1
   106	         0: invokedynamic #3,  0              // InvokeDynamic #0:lambda$:()Ljava/lang/Runnable;
   107	         5: invokestatic  #4                  // Method foo:(Ljava/lang/Runnable;)V
   108	         8: return        
   109	      LineNumberTable:
   110	        line 8: 5
   111	        line 9: 8

確かにinvokedynamic命令が生成されていることがわかります。このinvokedynamic命令に対応する「ブートストラップメソッド」は、javap出力の冒頭にある

     9	  BootstrapMethods:
    10	    0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metaFactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    11	      Method arguments:
    12	        #26 invokeinterface java/lang/Runnable.run:()V
    13	        #27 invokestatic Test.lambda$0:()V
    14	        #28 ()V

ここのところですね。ブートストラップメソッドが表している実体は、静的メソッドへのポインタであり、ここでは具体的には、JDKの提供するAPIであるメソッドjava.lang.invoke.LambdaMetafactory.metaFactory()です。

LambdaMetaFactory

ブートストラップメソッドとして仕掛けられているLambdaMetaFactoryは、このケースでは最終的にjava.lang.invoke.InnerClassLambdaMetafactory.javaのspinInnerClass()あたりの以下の処理を呼び出します。

(略)
import jdk.internal.org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;

(略)
    private final ClassWriter cw;                    // ASM class writer
(略)

    private Class<?> spinInnerClass() throws LambdaConversionException {
(略)    
        cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
                 lambdaClassName, null,
                 NAME_MAGIC_ACCESSOR_IMPL, interfaces);

        // Generate final fields to be filled in by constructor
        for (int i = 0; i < argTypes.length; i++) {
            FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argTypes[i].getDescriptor(),
                                            null, null);
            fv.visitEnd();
        }

        generateConstructor();

        MethodAnalyzer ma = new MethodAnalyzer();

        // Forward the SAM method
        if (ma.getSamMethod() == null) {
            throw new LambdaConversionException(String.format("Functional interface method not found: %s", samMethodType));
        } else {
            generateForwardingMethod(ma.getSamMethod(), false);
        }

        // Forward the bridges
        // @@@ The commented-out code is temporary, pending the VM's ability to bridge all methods on request
        // @@@ Once the VM can do fail-over, uncomment the !ma.wasDefaultMethodFound() test, and emit the appropriate
        // @@@ classfile attribute to request custom bridging.  See 8002092.
        if (!ma.getMethodsToBridge().isEmpty() /* && !ma.conflictFoundBetweenDefaultAndBridge() */ ) {
            for (Method m : ma.getMethodsToBridge()) {
                generateForwardingMethod(m, true);
            }
        }

        if (isSerializable) {
            generateWriteReplace();
        }

        cw.visitEnd();

        // Define the generated class in this VM.

        final byte[] classBytes = cw.toByteArray();

        ClassLoader loader = targetClass.getClassLoader(); // ★★
(略)
        return (Class<?>) Unsafe.getUnsafe().defineClass(lambdaClassName, classBytes, 0, classBytes.length,
                                                                   loader, pd);
    }

asmのClassWriterで生成したバイト列classBytesを、★★以降でクラスローダでロードしているようです。
どういうクラスを生成しているか、までは実は追ってないのですが、MethodHandle経由で渡してきたlambda$0をinvokeするようなコードを含むメソッドを含むクラスが生成されるのではないかと思います(もしくはlambda$0のバイトコード本体をコピーする??まさかね)。(2013/6/13削除)
こちらを見ると、直接lambda$0メソッドをinvokevirtual/invokestaticで呼んでいるようです(2013/6/13追記)。

まとめ

APIリファレンスが日本語に訳されたので、invokedynamicあたりのAPI(MethodHandle, CallSite,....)を初めて読む気になりました(へたれ)。ありがとうオラクルの人(寺田さん?)。

invokedynamicは今さらながらおもしろいな。今、Brainfuckindyで実装してみるテスト中です。

*1:この置き換えはinvokedynamicの通常動作です。CallSiteのインストールの話ね。

内部から見たVert.xとNode.jsとの比較

socket.ioがJavaGrailsから扱えるかを調べている関係でvert.xを調べていて興味深かったので、こちらにあるVert.xの記事を翻訳してみました。JGGUG G*Workshopにおける杉浦さんのVert.x資料もお奨めです。


Vert.xは急速に発達つつあるサーバ・フレームワークです。

世にあまたあるサーバ・フレームワークのいずれもが、多様なプロトコルをサポートし、高速であることが特長であると主張していますが、Vert.xはそれらよりも一歩抜きん出ています。例えば、Vert.xは、サーバサイドのネットワーク環境の確立と操作も対象としています。言いかえれば、Vert.xは、単一サーバ上のデーモン実行だけでなく、クラスタリング環境での複数サーバデーモンの実行を考慮しているのです。

したがって、Vert.xを調査するにあたっては、どのように高性能を実現しているかだけではなく、それが考慮しているネットワーク環境がどのようなものであるかを見ていくことが重要です。Vert.xの構造を調べることには、時間をかけるだけの価値があると思います。

Vert.xの哲学

Vert.xはNode.jsから影響を受けているプロジェクトです。Node.jsと同様に、Vert.xはイベントベースのプログラミング・モデルを提供するサーバ・フレームワークです。したがって、Vert.x APIは、Node.jsのそれと非常に似ています。なぜならこれらのモデルは両方とも非同期APIを提供するからです。

Node.jsはJavaScriptで開発されているのに対し、Vert.xはJavaで開発されています。しかし、単に「Node.jsのJavaバージョン」としてVert.xを理解するのは単純すぎる見方です。Vert.xはNode.jsの影響を受けていますが、Node.jsとは確かに異なるユニークな哲学を持っています。

Vert.xの設計哲学のコアは以下のように要約できます:

  • Polyglot…多言語対応
    Vert.xはJavaで作られていますが、Vert.xを使用するのにJavaを使う必要はありません。JavaやGroovyのようなJVMオペレーションに基づいた言語は当然ですが、Ruby, Python, JavaScriptなども使用することができます。JavaScriptを使用してサーバアプリケーションを構築する場合はNode.jsが競合になります。また、ScalaClojureのサポートも計画されています。
  • スーパーシンプルな並行実行モデル
    Vert.xを使ってサーバアプリケーションを構築する場合、単一スレッド・アプリケーションとしてコードを書くことができます。この事が意味するのは、同期やロック、volatile指定を使わなくてもマルチスレッド・プログラミングと同等の結果を得られるということです。
    Node.jsでは、JavaScript実行エンジンはマルチスレッドをサポートしないため、CPUコアをすべて利用するためには、複数の同じJavaScriptプログラムを実行しなければなりませんでした。しかしながら、Vert.xでは、1つのプロセス上でもCPUコア数に基づいた複数のスレッドを作成することができます。Vert.xでは、開発者がビジネス・ロジックの実装に集中できるようにマルチスレッディングを扱います。
  • イベント・バスの提供
    導入部で述べた様に、Vert.xの目標は単に「1つのサーバ処理デーモン」を開発することではありません。Vert.xは、相互に通信しあって動作する、Vert.xで構築された多様なサーバプログラム群を開発することを目標とします。このためVert.xはイベント・バスを提供し、ポイント・ツー・ポイント、あるいはPub/SubのようなMQ機能を使うことができます(イベント・バス機能を提供するために、Vert.xはインメモリ・データ・グリッドであるHazelcastmを使用します)。このイベント・バスを使い、異なる言語で構築されたサーバアプリケーション同士が容易に相互通信することができます。
  • モジュール・システムと公式モジュール・リポジトリ
    Vert.xはモジュール・システムを持っています。このモジュール・システムは一種のコンポーネントと思えば良いでしょう。つまり、Vert.xで構築したサーバアプリケーション・プロジェクトは、それ自身がモジュールになります。モジュールシステムの目的は再利用です。モジュールは公式モジュール・リポジトリに登録することができ、他者と共有することができます。

NettyとVert.xの関係、および違いは?

Vert.xの性能について議論する前に、NettyとVert.xの関係をまとめておくと、それは「Vert.xはNettyを使用する」ということです。言いかえれば、Vert.xのIOはすべてNettyによって処理されます。したがって、Vert.xとNettyのパフォーマンスの差異を検証することに意味はありません。

Vert.xは、Nettyとは異なる独立したAPIおよび機能を提供するサーバ・フレームワークであり、またNettyとは異なる目的で設計されています。Nettyは低レベルIOを処理するフレームワークであり、Vert.xはNettyよりも高いレベルのIOを処理します。

Node.jsとのパフォーマンスの比較

Vert.xによって提供される機能はNode.jsのものとは異なっているとしても、パフォーマンスの比較は重要です。以下の図1および図2は、Vert.x(JavaRuby、Groovy)とNode.jsの性能を示しています(ソースは: http://vertxproject.wordpress.com/2012/05/09/vert-x-vs-node-js-simple-http-benchmarks/)

図1は、HTTPサーバを構築し、200/OKレスポンスだけを返す場合のパフォーマンスの比較で、図2は、72バイトの静的なHTMLファイルがレスポンスとして返される場合のパフォーマンスの比較を示しています。

図1: 200/OKレスポンスだけが返された場合のパフォーマンスの比較

図2:72バイトのスタティックなHTMLファイルが返される場合のパフォーマンスの比較

上記のパフォーマンス比較はVert.x開発者によって提示されたものであり、テストは厳密な環境の下では行なわれていません。相対的な差異だけを見てください。

一点、注目すべきポイントとしては、Vert.xでのJavaScriptの性能がNode.jsよりも良いということです。とはいえ、この性能比較が信頼できるとしても、このことだけをもってして「Vert.xがNode.jsより良い」と言うのは難しいでしょう。Node.jsはSocket.ioのような素晴しいモデルを提供しており、参考資料も充実しているからです。

Vert.x用語集

Vert.xは独自の用語を定義したり、いくつかの一般的な用語をVert.x用に再定義しています。したがって、Vert.xを理解するためには、Vert.xで定義された用語を理解することが必要です。下記はVert.xを使う上で頻繁に使う用語です:

Verticle
Javaではmainメソッドを持ったクラスです。Verticleは、mainからさらに参照されるスクリプトを含んでいるかもしれません。さらにjarファイルやリソースを含んでいるかもしれません。アプリケーションは1つもしくはイベントバスで通信し合う複数のVerticleから構成されます。Javaに関しては、独立して実行可能なクラスあるいはJavaファイルとして理解することができます。
Vert.xインスタンス
VerticleはVert.xインスタンス内で実行されます。また、Vert.xインスタンスJVMインスタンス中で実行されます。したがって、単一のVert.xインスタンス中で多くのVerticlesを同時に実行することができます。Verticleは、それぞれ、自分自身のユニークなクラス・ローダーを持つことができます。こうすることで、staticメンバーおよびグローバル変数を通じたVerticles間の直接アクセスを防ぐことができます。多くのVerticleは、ネットワーク上の複数のホスト中で同時に実行することができます。また、Vert.xインスタンスはイベント・バスによってクラスタ化することができます。
並行処理
Verticleインスタンスは、常に同一のスレッド上で実行されることが保証されます。すべてのコードはシングルスレッド操作として開発することができ、Vert.xでの開発は容易になります。加えて、競合条件やデッドロックを防止します。
イベントベースのプログラミング・モデル
Node.jsフレームワークのように、Vert.xはイベントベースのプログラミング・モデルを提供します。Vert.xを使用してサーバをプログラムする場合、開発のためのほとんどのコードはイベントハンドラと関係があります。例えば、イベントハンドラを、TCPソケットからデータを受け取るように準備すると、それはデータ受信時に呼び出されます。さらに、イベントハンドラは「イベント・バスがメッセージを受け取った」「HTTPメッセージを受信した」「接続が切られた」「タイマーがタイムアウトした」などの通知を受け取ることができます。
イベントループ
Vert.xインスタンスは内部でスレッド・プールを管理しています。Vert.xは、スレッド・プールの数をCPUコア数になるべくぴったりと一致させます。スレッドはそれぞれイベント・ループを実行します。イベントループの実行中には、イベントの有無を確認します。例えば、ソケット中に読み取るべきデータがあるかどうか、イベントが発生したタイマーはどれか、などを確認します。イベントループ中で処理すべきイベントがある場合、Vert.xは対応するハンドラを呼びます(もちろんハンドラの処理期間が長すぎたりブロッキングI/Oがあったりした場合、追加処理が必要になります)。
メッセージパッシング
Verticlesはイベント・バスを通信に使用します。Verticleをアクターと仮定すると、メッセージパッシングはErlangで採用されていることで有名なアクターモデルに似ています。Vert.xサーバ・インスタンスは多くのVerticleインスタンスを持っており、インスタンス間でメッセージパッシングを行うことができます。したがって、システムはマルチスレッド配下でVerticleコードを実行することなく、使用可能なコア数を拡張することができます。
共有データ
メッセージパッシングは非常に有用です。しかしながら、それは必ずしもすべてのタイプのアプリケーション並列状況に対して最適なアプローチだとは限りません。キャッシュは、最もポピュラーなそういう例の1つです。1つのVerticleだけがキャッシュを保持しているのは非能率的です。他のVerticle群がキャッシュを必要とする場合、Verticleはそれぞれ同じキャッシュ・データを管理するべきです。したがって、Vert.xはグローバルなアクセス方法である共有マップを提供します。Verticlesは不変のデータだけを共有します。
Vert.xコア
その名の通り、これはVert.xの中核機能です。Verticleが直接呼ぶことができる関数はコアに含まれています。Vert.xがサポートする各プログラミング言語APIは、コアにアクセスすることができます。
Vert.xアーキテクチャ
Vert.xの単純なアーキテクチャーは次の図3に示されます。

図3:Vert.xアーキテクチャー(出所: http://www.javacodegeeks.com/2012/07/osgi-case-study-modular-vertx.html )

Vert.xのデフォルト実行ユニットはVerticleです。また、いくつかのVerticleは、同時に1つのVert.xインスタンス上で実行することができます。Verticleはイベントループ・スレッド上で実行されます。Vert.xインスタンス群はネットワーク上の1つのホストだけでなく、複数のホスト上でも実行することができます。この際、Verticlesやモジュールはイベントバスで通信します。
まとめると、vert.xアプリケーションはVerticle群とモジュールで構成されており、それらの通信はイベント・バスで行います。

vert.xプロジェクト構造
下記は、Vert.x Githubページからコピーしたソース・コードのVert.xプロジェクト構造をExlipseで表示しているところです。


図4:Vert.xソースのツリー。


全体の構成は以下のとおりです:

  • vertx-coreはコアライブラリーです。
  • vertx-platformは分散処理とライフ・サイクルを管理します。
  • vert-langコアJava APIを言語に提供するときに使われます。

Gradleがそのプロジェクトビルドシステムとして採用されています。antとMavenよりも優れた点があります。

Vert.xのインストールおよび単純な例の実行

Vert.xは、Java 7のinvokeDynamicを使用するため、JDK7が必須です。Vert.xのインストールは容易です。

  1. http://vertx.io/downloads.htmlから任意のフォルダに圧縮したインストール・ファイルをダウンロードしてください。
  2. ファイルを解凍し、binディレクトリをPATH環境変数に加えてください。

以上がすべてです(訳注: gvm Toolを使ってもvert.xをインストール可能です)。コマンド・ウィンドウ中で、vertx versionを実行してください。バージョン情報が成功裡に表示されれば、インストールは完了です。

例1

さて、JavaScriptで「Hello World!」を表示する単純なWebサーバーを構築し実行しましょう。
次のコードをserver.jsというファイルに保存してください。
これは、Node.jsのコードとほとんど同一です。

  load('vertx.js');

  vertx.createHttpServer().requestHandler(function(req) {
  req.response.end("Hello World!");}) .listen(8080, 'localhost');

vertxコマンドを使用して、作成したserver.jsアプリケーションを実行します:

  $ vertx run server.js

ブラウザでhttp://localhost:8080を開き、「Hello World!」が表示されれば成功です。

例2

別の例を、別の言語を使ってやってみましょう。次のコードはJavaで書かれています。静的ファイルの内容を読みとって、HTTPレスポンスとして返すWebサーバーです。

  Vertx vertx = Vertx.newVertx();
  vertx.createHttpServer().requestHandler(new Handler() {
      public void handle(HttpServerRequest req) {
          String file = req.path.equals("/") ? "index.html" : req.path;
          req.response.sendFile("webroot/" + file);
      }
  }).listen(8080);

次のコードは、まったく同じ機能を持ちますがGroovyで書いた場合です:

  def vertx = Vertx.newVertx()
  vertx.createHttpServer().requestHandler { req ->
  def file = req.uri == "/" ? "index.html" : req.uri
  req.response.sendFile "webroot/$file"
  }.listen(8080)

Vert.xとNHNの将来

NHN社において我々はVert.xを高く評価しており、公式リリース以前からVert.x開発に注目しています。私たちはVert.xを改善について議論するために2012年6月以来、Vert.xの主な開発者(Tim Fox)と連絡を取っています。例えば、Vert.xの上のSocket.ioについてです。

Socket.ioはNode.jsだけで利用可能でしたが、私たちはJavaに移植し、Gitubの上のVert.xリポジトリにPull Requestを送りhttps://github.com/vert-x/vert.x/pull/320、今まさに、Vert.x-modプロジェクトにマージされるところです。私たちの努力であるsocket.io vert.xモジュールは、NHNで開発しているRTCS 2.0バージョン(vert.x+Socket.io)で使用されます。

Node.jsの人気の理由の一つは、Socket.ioのためであるかもしれず、Vert.xでもSocket.ioを使用できるようになれば、Vert.xの用途は広がるかもしれません。更に、このsocket.io vertxモジュールを埋め込みライブラリとして使用することで、Javaベースのアプリケーション中でsocket.ioを使用できるようになります。

RTCSって何

RTCS(瞬時通信システム)はNHNによって作られたリアル・タイム・ウェブ開発プラットフォームであり、ブラウザとサーバ間のメッセージのリアル・タイム転送を支援します。
RTCSはBaseball 9、Me2Dayチャット、BANDチャットのようなNHNウェブサービスのために使われています。

まとめ

Vert.xの最初のバージョンは2012年5月にリリースされたので、2009年に最初のバージョンがリリースされたNode.jsと比較すると、Vert.xの歴史は非常に短く、参考資料は多くありません。しかし、Vert.xはVMwareに支援されており、CloudFoundryの上で動作することもできます。なので、多くの参考資料がすぐに入手可能になるだろうと期待しています。

著者

Web Platform Development Labのソフトウェア・エンジニア、Seongmin Wooによる。

参照