clojureを分解する(3)
man java してたら、面白いオプションが見つかったので、ちょっと試してみます。
sakae@debian:~/clj$ java -verbose -jar clojure.jar
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
:
user=> [Loaded clojure.main$repl__5158$fn__5173 from file:/home/sakae/clj/clojure.jar]
(+ 1 2)
[Loaded clojure.lang.RT$6 from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$C from file:/home/sakae/clj/clojure.jar]
:
[Loaded clojure.asm.Frame from file:/home/sakae/clj/clojure.jar]
[Loaded user$eval__1 from clojure.lang.DynamicClassLoader]
3
clojureを起動してから、promptが出てくるまでに、幾つのクラスがロードされているか
調べてみましたよ。結果は 1315個、そのうち、clojureに関するものは、722個でした。
残りは、Sun提供のやつですね。
クラスローダーが、jarファイルを展開しながら、ヒープ領域に配置していく訳ですから
起動完了に時間がかかるのもうなずけます。
また、clojureが起動した後でも、関数呼び出しを行ったりすると、追加でクラスが
ローディングされます。
user=> (defn v2 [v] (* v 2))
[Loaded clojure.lang.Compiler$MapExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$KeywordExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$StringExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$DefExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.core$fn__3979$psig__3981 from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.core$map__3371$fn__3374 from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$1 from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$LocalBindingExpr from file:/home/sakae/clj/clojure.jar]
[Loaded user$v2__4 from clojure.lang.DynamicClassLoader]
#'user/v2
関数を定義する場合は、コンパイラーがロードされました。
user=> (v2 5)
[Loaded clojure.lang.Compiler$InvokeExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$AssignableExpr from file:/home/sakae/clj/clojure.jar]
[Loaded clojure.lang.Compiler$VarExpr from file:/home/sakae/clj/clojure.jar]
[Loaded user$eval__7 from clojure.lang.DynamicClassLoader]
10
user=> (v2 6)
[Loaded user$eval__10 from clojure.lang.DynamicClassLoader]
12
関数の実行時にも、コンパイラーがロードされましたよ。一度、ロードされると
ヒープに残るので、さすがに、同じクラスをロードする事は無いようです。
jvmの裏側を垣間見れて、興味深いです。また、clojureも、関数定義はコンパイル結果を
保存してると予想されます。
どんな風にコンパイルされるのでしょう? 本来なら、上記で定義した v2 を見たい
ですが、現段階では、どこに保存されているか分かりませんので、別のものを調べます。
clojureを作り出す時、clojure.lang.Compile を呼んで、main.clj 等の Lispソースを
コンパイルしてました。多分、同じようにコンパイルされるはずなので、jarファイル中
にある、短めのものを選んでみます。(clojure.jar を、tmpに展開しました)
一番短いソースは、set.cljと言う、集合関係のライブラリーでした。
sakae@debian:/tmp/clojure$ ls set*
set$difference__5232.class set$project__5245$fn__5247.class
set$index__5266$fn__5268.class set$project__5245.class
set$index__5266.class set$rename__5260$fn__5262.class
set$intersection__5235.class set$rename__5260.class
set$join__5284$fn__5287$fn__5289.class set$rename_keys__5251$fn__5254.class
set$join__5284$fn__5287.class set$rename_keys__5251.class
set$join__5284$fn__5295$fn__5297.class set$select__5238$fn__5240.class
set$join__5284$fn__5295.class set$select__5238.class
set$join__5284.class set$union__5229.class
set$map_invert__5272$fn__5275.class set.clj
set$map_invert__5272.class set__init.class
予想では、set$ 以降の文字列は、Lispの関数名でしょう。その後につづく数字は
通し番号と思われます。余り大きな関数は、都合のよいように分割でしょうね。
とりあえず、union というのを見てみましょう。
(defn union
"Returns a set that is the union of the two sets."
[xset yset]
(reduce conj xset yset))
さて、これと対になる(であろう)set$union__5229.class を、DisAsmします。
ファイル名の途中に、初心者お断りの $ が、混じっていますんで、注意します。
sakae@debian:/tmp/clojure$ javap -c set\$union__5229
Compiled from "set.clj"
public class clojure.set$union__5229 extends clojure.lang.AFunction{
public static final clojure.lang.Var const__0;
public static final clojure.lang.Var const__1;
public static {};
Code:
0: ldc #12; //String #=(var clojure.core/reduce)
2: invokestatic #18; //Method clojure/lang/RT.readString:(Ljava/lang/String;)Ljava/lang/Object;
5: checkcast #20; //class clojure/lang/Var
8: putstatic #22; //Field const__0:Lclojure/lang/Var;
11: ldc #24; //String #=(var clojure.core/conj)
13: invokestatic #18; //Method clojure/lang/RT.readString:(Ljava/lang/String;)Ljava/lang/Object;
16: checkcast #20; //class clojure/lang/Var
19: putstatic #26; //Field const__1:Lclojure/lang/Var;
22: return
public clojure.set$union__5229();
Code:
0: aload_0
1: invokespecial #29; //Method clojure/lang/AFunction."":()V
4: return
public java.lang.Object invoke(java.lang.Object, java.lang.Object) throws java.lang.Exception;
Code:
0: getstatic #22; //Field const__0:Lclojure/lang/Var;
3: invokevirtual #37; //Method clojure/lang/Var.get:()Ljava/lang/Object;
6: checkcast #39; //class clojure/lang/IFn
9: getstatic #26; //Field const__1:Lclojure/lang/Var;
12: invokevirtual #37; //Method clojure/lang/Var.get:()Ljava/lang/Object;
15: aload_1
16: aload_2
17: aconst_null
18: astore_1
19: aconst_null
20: astore_2
21: invokeinterface #42, 4; //InterfaceMethod clojure/lang/IFn.invoke:(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
26: areturn
}
わぉー、いきなり、JVMアセンブラーの世界ですね。仕様書を読み直してから
出直してきます。どうしても理解出来なかったら、gclで有名な湯浅先生の所へ
留学したいぞ。先生は、JVMの仕様書を読んで、Lisp用の命令を追加し、早い
Schemeを開発されちゃいましたから。どんだけ、凄い人なんだ。
所で、私、大変な事にきづいてしまいました。
clojureって、Lispの片割れなのに、ガベコレのソースが無いんです。
どこを探してもありません。ひょっとして、作者さんが、ガベコレなんて面倒だから
やーめたと、放棄してしまったのでしょうか。
ypsilonを開発されちゃった藤田さんの話を聞く機会がありましたが、ガベコレに関しては
涙なくして語れない程の苦労をなさっています。
rubyもしかりで、rubyが遅い一因は、ガベコレにあると、やり玉に上がっています。
clojureの作者さんの気持ち、よーくわかりますよ。
JVMって、天然のガベコレ付きですから、車輪の再発明は必要ないと。。。
うまい選択だなあ。ほんま、感心するよ!!!