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って、天然のガベコレ付きですから、車輪の再発明は必要ないと。。。
うまい選択だなあ。ほんま、感心するよ!!!