周知: 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を実装してみよう!
唐突ですが、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なので。
詳しくはこちらかこちら。
では!!
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ですよ
んで、来るべきGrails 2.3ではHATEOASなアプリを簡単に書けるようになるよ、と。
しかし、なんて読むんだろう。ハテオアス?
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,....)を初めて読む気になりました(へたれ)。ありがとうオラクルの人(寺田さん?)。
*1:この置き換えはinvokedynamicの通常動作です。CallSiteのインストールの話ね。
内部から見たVert.xとNode.jsとの比較
socket.ioがJavaやGrailsから扱えるかを調べている関係で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が競合になります。また、ScalaとClojureのサポートも計画されています。
- スーパーシンプルな並行実行モデル
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(Java、Ruby、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は、コアにアクセスすることができます。
図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ソースのツリー。
全体の構成は以下のとおりです:
Vert.xのインストールおよび単純な例の実行
Vert.xは、Java 7のinvokeDynamicを使用するため、JDK7が必須です。Vert.xのインストールは容易です。
- http://vertx.io/downloads.htmlから任意のフォルダに圧縮したインストール・ファイルをダウンロードしてください。
- ファイルを解凍し、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による。
参照
- 「主なマニュアル」http://vertx.io/manual.html
- 「インストール・ガイド」http://vertx.io/install.html
- 「C10K問題」http://www.kegel.com/c10k.html Gim Seongbak、ソングJihun、Hanbitメディア2004「Java I/O & NIOネットワーク・プログラミング。」