Nailgun/GroovyServを使ってClojureの起動を高速化する
JVM上で動作する言語(処理系)にはよくある話だけれど,Clojureの起動は遅い。下の結果は手元の環境で実行した場合の例。
$ repeat 5 time java clojure.main -e '(println "Hello, Clojure!")' Hello, Clojure! java clojure.main -e '(println "Hello, Clojure!")' 2.31s user 0.17s system 131% cpu 1.885 total Hello, Clojure! java clojure.main -e '(println "Hello, Clojure!")' 2.31s user 0.17s system 131% cpu 1.891 total Hello, Clojure! java clojure.main -e '(println "Hello, Clojure!")' 2.26s user 0.16s system 134% cpu 1.798 total Hello, Clojure! java clojure.main -e '(println "Hello, Clojure!")' 2.28s user 0.16s system 133% cpu 1.841 total Hello, Clojure! java clojure.main -e '(println "Hello, Clojure!")' 2.24s user 0.16s system 128% cpu 1.863 total $
2秒程度だと大したことはないように思えるが,繰り返し実行するスクリプトのような使い方をしていると,この少しの時間の積み重ねが目についたりする。
JVM上で動くプログラムの起動を高速化するツールがいくつか開発されているようで,以下のようなものがあるらしい。
NailgunはJavaプログラム向け,GroovyServは基本的にはGroovyプログラム向けのようだけど,GroovyからJavaのクラスにアクセスできるので,一般のJavaプログラムで利用できる。どちらもあらかじめJVMプロセス(サーバ)を起動させておいて,Cで書かれたクライアントから処理をディスパッチすることで都度の起動を高速化する仕組み。
インストール方法の説明等は他所へ譲るとして,以下ではNailgun/GroovyServでClojureを実行する方法について書いていく。
Nailgunでの起動
Nailgunのサーバを起動するには,Nailgun付属のJARを呼び出せばいい。
$ java -server -jar nailgun-0.7.1.jar &
-serverオプションはお好みで。ちなみに,Homebrewでインストールしたら ng-server というコマンドが使えるようになっていたが,やってることは上とまったく同じようだ。
サーバを起動したら ng コマンドからClojureを起動すればいいが,その前にクラスパスを指定する。
$ ng ng-cp /path/to/clojure.jar
クラスパスは一度指定するとサーバプロセスが生きている間は有効になっている。
クラスパスの指定ができたらClojureを起動する。
$ ng clojure.main Clojure 1.2.0 user=> $
最初の起動は若干遅いが,二度目以降は高速に起動できる。
$ time ng clojure.main -e '(println "Hello, Clojure")' Hello, Clojure ng clojure.main -e '(println "Hello, Clojure")' 0.00s user 0.00s system 7% cpu 0.044 total $
GroovyServでの起動
GroovyServでは,最初のクライアント呼び出し時にサーバが起動されるため,あらかじめサーバを起動しておく必要はない。
groovyclient コマンドに-eオプションを付けると,実行するGroovyスクリプトを指定できる。GroovyServの小技シリーズ2 scalacを高速化する - uehaj's blogを参考にすると,
$ groovyclient -cp クラスパス -e '実行するクラス.main(args)'
とすると,実行するクラスのmainメソッドを実行できるようだ。
GroovyServでClojureを実行しようとするとここで問題が起きる。Clojureのインタプリタは,clojure.mainクラスのmainメソッドから実行されるが,"clojure.main"というクラス名は最初の一文字が大文字ないため,Groovyからはクラス名として認識されないようだ。
解決策としては,リフレクション経由で呼び出すか,
$ groovyclient -cp /path/to/clojure.jar -e 'Class.forName("clojure.main").getMethod("main", ([String] as Class)).invoke(null, ([args] as Object[]))' Clojure 1.2.0 user=>
あるいは,単にclojure.main.mainを呼び出すだけのJavaクラスを書いて,それを呼び出すようにする。
public class GSClojure { public static void main(String[] args) throws Exception { clojure.main.main(args); } }
$ groovyclient -e 'GSClojure.main(args)' Clojure 1.2.0 user=>
起動時間はNailgunと比較するとやや遅いが,それでも通常の起動時間より10倍以上速い。
$ time groovyclient -e 'GSClojure.main(args)' -- -e '(println "Hello, Clojure!")' Hello, Clojure! groovyclient -e 'GSClojure.main(args)' -- -e '(println "Hello, Clojure!")' 0.00s user 0.00s system 1% cpu 0.168 total $
NailgunとGroovyの違い
NailgunとGroovyの違いについて,以前GroovyServの開発者の @uehaj さんから教えていただいたので,ツイートを載せておく。
@athos0220 逆にnailgunの面白いところは、aliasを定義できるところとか。
2011-04-10 11:19:54 via YoruFukurou to @athos0220
あとは,Nailgunの方は開発が止まっているのに対して,GroovyServは先日0.7がリリースされる等,開発が活発に行われているということも注意する点として挙げられるかなぁと。