uehaj's blog

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

Groovyserv予告編

id:kiy0takaさんが、d:id:kiy0taka:20100120#1264001023にて「Groovyの起動を速くするという記事を書かれています。非常に短く、簡潔に実装されています。-lオプションの良い利用例ですね。コマンド名もいい。

あと、そうなんですよ。言いっぱなしになってますが、実はオリジナルで作っているkobo版「Groovyserv」というものの実装が進んでいて、原理は同じなのですが、

  • クライアントはC言語で起動速度を最短化
  • MacOSX,Windows(cygwin),Linuxで動作確認済み
  • クライアントは、初回起動時にサーバが動作してなければサーバを起動する。つまり透過的に利用できる(初回起動時は遅いけど)。
  • セキュリティ上の懸念から、TCPポートへの接続は、一応localhostからのもののみに制限している(でもログインしている他のユーザからは叩けちゃうけど)。
  • クライアント-サーバ間は独自プロトコルで通信
  • クライアントは自身の標準入力をサーバに送信し、サーバで標準出力/エラー出力に出力された内容をクライアントに転送する。受け取ったクライアントは、それを自身の標準出力/標準エラーに書き出す。要はフィルタが書けるってことです。
  • クライアントは自身のカレントディレクトリをサーバに送信し,サーバは受け取ったカレントディレクトリをJNA(Java Native Access)でネイティブレベルで設定しつつ実行。これによりコマンドラインでファイルを相対指定可能になる。
  • 複数のクライアントがサーバに同時接続してきたときは、サーバー側のSystem.out/in/errは対応クライアントと接続されるように多重化(複数クライアントの同時接続は、特に複数のgroovyスクリプトをパイプでつないでフィルタとして動作させるために必要)
  • サーバはスクリプト起動ごとに動的にクラスパスを設定して実行。つまりCLASSPATH環境変数はクライアント実行時のものが使われるし、クライアント実行時に指定した-cpオプションも機能する
  • Groovyのバージョンには今のところ一応依存しない(1.6.x以降、1.8SNAPSHOTまでで確認)
  • サーバ側でのSystem.exit()の実行を、セキュリティマネージャでトラップかけて、クライアントにexit statusを送り返し,クライアントがそのexit statusで終了

などなど。
でも問題点も発覚していて、

  • 動的なクラスパスの設定がMacOSでは動作しない。Linux/WindowsではOKなのだが・・。JVMの問題かなあ。
  • 一日中走らせて頻繁に実行してるとヒープのpermgen領域が枯渇する!
  • 標準入出力やエラー出力の多重化は、System.out/in/errをひっかけてスレッドローカル化しているのですが、サーバで受け付けたスレッドは良いのですが、そこからnew Thread()したスレッドからのSystem.out/in/errをキャプチャする事ができない。

など。いずれもなかなか厄介です。OSSにする予定なのですが、まだ整理できてなくて&忙しくなってしまって、公開できておりませんごめんなしあ。近日公開予定です。待てぬ、是非使いたい!というご興味のある方はおしらせください。

性能的には、groovyコマンドの起動が数十倍とかに高速化されるので「スクリプトらしい」groovyを味わう事ができます。以下、timeコマンドの結果(以下、MacOS Xの例。ちゃんとははかっていませんが、LinuxMacOSで高速化が割と顕著な気がする)。

$ time groovy -e "println 'hello'"
hello

real 0m0.072s
user 0m0.002s
sys 0m0.006s

$ time \groovy -e "println 'hello'"
hello

real 0m6.382s
user 0m1.577s
sys 0m0.304s

groovyはクライアントであるgroovyclientコマンドへのaliasにしてます*1。上の「\groovy」が元のgroovyの実行結果です。
以下は参考にrubyの結果。

$ time ruby -e "print 'hello'"
hello
real 0m0.097s
user 0m0.003s
sys 0m0.004s

わっはっは。

動的言語であるGroovyは、テストや試行錯誤を繰り返す事が、静的言語に比べて比較的多くなります。しかし、GroovyはJava以上に起動が遅いと言うのが定説です*2。Groovyservを使う事で、それが端的に0コンマん秒ぐらいになるので、テストコードなど、頻繁に実行しなければならない場合になどたいへん結構うれしいとの感想を頂いています。

*1:同様に、groovyc,gradle,groovyConsole, groovyshなどのコマンド群も、grooyclientを使って呼び出す事で起動を高速化できる。ただ、groovyConsoleのようにもともと起動しっ放して使うものだとあまりうれしくない。

*2:なんで遅いかって言うと、数多くのクラスを読み込んでいる、ということの他に、Groovyでは基盤としてクラスローダフレームワーク[http://classworlds.codehaus.org/:ClassWorlds]っていうのを使っていて、これで動的に柔軟にクラスパスを設定できるものなんですが、起動速度に悪影響を及ぼしているようです。