Wednesday, May 9, 2012

InvokedynamicでGroovyライフをリスタートしよう

Groovyは5/7にリリースされたバージョン2.0 beta 3でinvokedynamic (indy)に対応しました。これはindyであなたのGroovyライフをリスタートする方法をお見せするだけの短い投稿です。Indyはダイナミックなメソッド呼び出しのためのJava SE 7の新たなバイトコード命令です。もしあなたがindyについて知らないのであればJSRを読んでください。

さぁ、では始めましょう。まずはindyに対応したバージョンのGroovyをインストールしてください。現時点(2.0 beta 3)ではindyはデフォルトで対応されていないためです。次のいずれかの方法が選べます。

* アーカイブをダウンロードしてlib/内のgroovy.jarあるいはembeddable/内のgroovy-all.jarの代わりにindy/にあるgroovy-indy.jarかgroovy-all-indy.jarを使う。

* indyを有効にするオプションを使ってソースからビルドする

ant install -DuseIndy=true -DskipTests=true

2番目に、あなたの目的に応じた方法で次のようにindyを有効にするオプションを付けてコンパイルします:

* groovy

groovy --indy YourScript.groovy

* groovyc

groovyc --indy YourScript.groovy

* GroovyShell class

import org.codehaus.groovy.control.CompilerConfiguration

def conf = new CompilerConfiguration()
conf.optimizationOptions.indy = true
def shell = new GroovyShell(conf)
shell.evaluate(/* your script */)

これだけです! それでは良いGroovyライフを!

Friday, May 4, 2012

Exploring JavaFX 2 - アプリケーションパラメタへのアクセス

JavaFXはApplicationクラスオブジェクトからアプリケーションパラメタにアクセスする方法としてApplication.getParameters()を提供します。例えば、ユーザがGNU形式でオプション(名前付きパラメタ)に幅と高さ、引数(名前なしパラメタ)にメッセージを指定すれば:
java AccessApplicationParametersDemo --width=200 --height=100 hello
アプリケーションは次のようにパラメタを受け取れます:
public class AccessApplicationParametersDemo {

    public static void main(String[] args) {
        Application.launch(UsingGetParametersApplication.class, args);
    }

}
public class UsingGetParametersApplication extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Map opts = getParameters().getNamed();
        System.out.println("width=" + opts.get("width"));
        System.out.println("height=" + opts.get("height"));
        
        List args = getParameters().getUnnamed(); 
        System.out.println("message=" + args.get(0));
    }

}
但しgetParameters()は今のところ(v2.2-b06)オプションのパーサが貧弱で使い勝手がよくありません。省略オプションやPOSIX形式に対応してなく、次の形式のどれも扱えません:
-w=200 -h=100
--width 200 --height 100
-w 200 -h 100
getParemters()をカスタマイズすればいいでしょうか?しかし実装クラスへのアクセスが中でハードコードされていて、メソッドがfinalだと聞いたらどうでしょう?つまりgetParameters()から直接オプションや引数を取得するのは今のところ良い選択ではありません。getParameters()には運搬役に徹してもらい、主な作業はApache Commons CLIに任せるのがお勧めです:
public class UsingCommonsCliApplication extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Options options = new Options();
        options.addOption("w", "width", true, "");
        options.addOption("h", "height", true, "");
        CommandLineParser parser = new PosixParser();
        CommandLine cmd = parser.parse(
            options, 
            getParameters().getRaw().toArray(
                new String[getParameters().getRaw().size()]
            )
        );
        System.out.println("width=" + cmd.getOptionValue("width")); 
        System.out.println("height=" + cmd.getOptionValue("height")); 
        System.out.println("message=" + cmd.getArgList().get(0));
    }

}
この問題に対するJavaFXチームからの回答は"将来のバージョンでは検討可能"ということなのでそのうち解消されるかもしれません。そもそもこのような機能はパラメタを固定された形式で受けとるAppletやJWSでのみ必要とされるはずなので、JavaFXはAppletApplicationやJWSApplicationのような各環境用のクラスを持ち、それらにのみこの機能を持たせる方が良さそうです。

Tuesday, March 20, 2012

正しいGroovyベンチマーク

GroovyのようなJVM上に乗っている言語のベンチマークは難しく、安易に行うと誤った結果を出すことになります。例えばStringBuilderとStringBufferの比較するため次のようなプログラムを書いたとします。StringBufferは同期の為にStringBuilderよりも遅いことがよく知られています:
def n = 100 * 1000
def at, bt

bt = System.nanoTime()
n.times {
    def sb = new StringBuilder()    
    sb.append('foo')
    sb.append('bar')
    sb.append('baz')
}
at = System.nanoTime()
println((at - bt) / n) 

bt = System.nanoTime()
n.times {
    def sb = new StringBuffer()    
    sb.append('foo')
    sb.append('bar')
    sb.append('baz')
}
at = System.nanoTime()
println((at - bt) / n) 

しかし期待とは異なりStringBufferがStringBuilderよりも2倍以上も速い結果になりました(Groovy 1.8.6、JVM 1.7.0_04-ea Server VM)。最適化により同期のコストがなくなったとしても同等以下のはずであり、この計測は完全に失敗しています:
StringBuilder   1947.3 (2.76)
StringBuffer     703.1 (1)

StringBuilder同士で比較すれば失敗はより明らかとなります:
StringBuilder #1   2030.68 (4.85)
StringBuilder #2    418.47 (1)

何が問題だったのかは実行時間の推移を見るとわかります。1回目の計測中に最適化が行われ、2回目は最適化がある程度完了した状態から開始されています。そのため1回目は2回目より不利となります。また、2回目の計測中に飛び出ている数値があります。これはガベージコレクションによるものですが、この時に回収されるゴミは1回目の計測中に出たものを含みます。この点では逆に2回目が1回目より不利となります。つまり正しいベンチマークとは同じスタートラインに立たせることであり、そのためには計測前に最適化を完了させメモリを掃除しゴミ箱を空にしておく必要があります。


これらの問題を自身で解決するのもよいですが、幸運なことにGroovyにはベンチマークフレームワーク、GBenchがあります。GBench 0.3.0はあなたに代わってこれらの面倒な課題を解決し正しい結果を出す機能を持っています。GBenchを使って書き直したのが次のコードです。measureCpuTimeをfalseにしCPU時間の計測を無効にすると実行時間のみの簡潔なレポートを受け取れます:
import gbench.*

new BenchmarkBuilder().run(measureCpuTime:false) {
    'StringBuilder' {
        def sb = new StringBuilder()        
        sb.append('foo')
        sb.append('bar')
        sb.append('baz')
        sb.toString()
    }
    'StringBuffer' {
        def sb = new StringBuffer()        
        sb.append('foo')
        sb.append('bar')
        sb.append('baz')
        sb.toString()
    }
}.prettyPrint()

このコードの実行結果は次のようになりました。差がそれ程ないのは私の環境がServer VMであり、ロック周りが最適化される為です。ロックの最適化ついてはJeroen Borgersの記事、Java 6のスレッド最適化は実際に動作しているのか?が参考になります:
StringBuilder   244 (1)
StringBuffer    265 (1.08)

最適化を無効にした(-XX:-DoEscapeAnalysis -XX:-EliminateLocks -XX:-UseBiasedLocking)結果は以下のとおりでした:
StringBuilder   242 (1)
StringBuffer    310 (1.28)