Saturday, June 25, 2011

GBench = @Benchmark Annotation + BenchmarkBuilder

Groovy 用のベンチマーク・フレームワーク、 GBench をリリースしました。GBench は2つの機能、 @Benchmark Annotation と BenchmarkBuilder を持っています @Benchmark Annotation はプロダクトのコードをいじらずに実行時間を計測することを可能にするアノテーションで、既に公開されています。より詳しい情報については以前のポストを読んでください。 BenchmarkBuilder はベンチマーク・コードを簡単に書くための便利なビルダで、今回初めて登場します。

次のコードは文字列連結を1000回繰り返してベンチマークを採るものです:
----
def strings = ['GBench', ' = ', '@Benchmark Annotation', ' + ', 'BenchmarkBuilder']
def benchmark = new BenchmarkBuilder()
benchmark.run times: 1000, {
    with '+=', { 
        def s = ''
        for (string in strings) {
            s += string    
        }
    }
    with 'concat', { 
        def s = ''
        for (string in strings) {
            s.concat string    
        }
    }
    with 'string builder', {
        def sb = new StringBuilder()    
        for (string in strings) {
            sb << string    
        }
        sb.toString() 
    }
    with 'join', {
        strings.join()
    }
}
println benchmark
----

出力はこのようになります:
----
                   time
+=             18197705
concat          7669621
string builder  9420539
join            5248348
----

もちろん結果はソートできます:
----
println benchmark.sort({ lhs, rhs -> lhs.time <=> rhs.time })
----

----
                   time
join            5248348
concat          7669621
string builder  9420539
+=             18197705
----

結果を好きなように処理することもできます:
----
new File('benchmark.csv').withWriter { file ->
    file.writeLine 'label,time(ms)'
    benchmark.sort().each { bm ->
        file.writeLine "${bm.label},${bm.time / 1000000}"
    }
}
----

----
> cat benchmark.csv
label,time(ms)
join,5.248348
concat,7.669621
string builder,9.420539
+=,18.197705
----

いまのところ、GBench は wall-clock time しか計測できませんが、将来のリリースでは CPU time や user time もサポートする予定です。

GBench はこちらからダウンロードできます。ぜひ試してフィードバックしてください!

Saturday, June 11, 2011

@Benchmark annotation for Groovy が大幅に更新!

@Benchmark annotation for Groovy v11.06.11 をリリースしました。このリリースはいくつかの大きな変更と大きなバグの修正を含んでいます。

- 変更
  - ベンチマーク結果へのクラス/メソッド情報の追加
  - ベンチマーク結果処理をカスタマイズする新オプション

  - クラスアノテーションのサポート

- バグ修正
  - クラス内のメソッドで動かない問題を修正


ベンチマーク結果へのクラス/メソッド情報の追加

ベンチマーク結果として、ベンチマークされたメソッドとそのクラス上方を取得できます。例えば次のコードでは、
----
package foo

class Foo {
    @Benchmark
    def foo() {
    }
}
----

出力はこうなります:
----
foo.Foo java.lang.Object foo(): xxx ns
----


ベンチマーク結果処理をカスタマイズする新オプション

貧弱なオプション、"prefix" と "suffix" の代わりに ベンチマーク結果の処理をカスタマイズする "value" オプションが追加されました。値の設定方法は3つあります:

- ハンドラクラスを使う
- クロージャを使う
- システムプロパティを使う

ハンドラクラスを使う場合、Benchmark.BenchmarkHandler インターフェイスを実装したクラスを作り2つのメソッド、 handle() と getInstance() をそれらへ追加します:
----
class MyHandler implements Benchmark.BenchmarkHandler {
    static def instance = new MyHandler()
    static MyHandler getInstance() {
        instance    
    }
    void handle(klass, method, time) {
        println("${method} of ${klass}: ${(time/1000000) as long} ms")
    }    
}
----

そうですね、上の例のようなシングルトンクラスは @Singleton アノテーションを使えば短く書けます :-)
----
@Singleton
class MyHandler implements Benchmark.BenchmarkHandler {
    void handle(klass, method, time) {
        println("${method} of ${klass}: ${(time/1000000) as long} ms")
    }
}
----

最後にハンドラクラスを @Benchmark に設定します:
----
@Benchmark(MyHandler.class)
def foo() {
}
----

Groovy 1.8 からはハンドラクラスの代わりにクロージャも使えます。クロージャであれば、
ベンチマーク結果を処理するクロージャを設定するだけです:
----
@Benchmark({println("${method} of ${klass}: ${(time/1000000) as long} ms")})
def foo() {
}
----

またシステムプロパティ、 "groovybenchmark.sf.net.defaulthandle" でデフォルトの処理を置き換えられます:
----
> groovy -cp groovybenchmark-11.06.11.jar 
-Dgroovybenchmark.sf.net.defaulthandle="println(method + ' of ' + klass + ': ' + ((time/1000000) as long) + ' ms')" foo\Foo.groovy
----

これらの例の場合、出力はこうなります:
----
java.lang.Object foo() of foo.Foo: xxx ms
----


クラスアノテーションのサポート

クラスにアノテートすることで、そのクラスの全てのメソッドのベンチマーク結果を取得できます:
----
package foo

@Benchmark
class Foo {
    def foo() {
    }
    def bar() {
    }
}
----

この例の場合、 foo() と bar() メソッドのベンチマーク結果を取得できます:
----
foo.Foo java.lang.Object foo(): xxxx ns
foo.Foo java.lang.Object bar(): xxxx ns
----

そしてこれはつまり、あなたがコードの変更やプロファイリングツールなしにプログラムの全てのメソッドをベンチマークできる力を手に入れたことを意味します。なぜなら Groovy 1.8 は全てのクラスにアノテーションを適用する compilation customizer を提供しているからです:
----
// BenchmarkGroovyc.groovy
import groovybenchmark.sf.net.Benchmark

import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.tools.FileSystemCompiler

def cc = new CompilerConfiguration()
cc.addCompilationCustomizers(new ASTTransformationCustomizer(Benchmark))
new FileSystemCompiler(cc).commandLineCompile(args)
----

----
> groovy -cp groovybenchmark-11.06.11.jar BenchmarkGroovyc.groovy MyApp.groovy
> java -cp .;%GROOVY_HOME%\embeddable\groovy-all-1.8.0.jar;groovybenchmark-11.06.11.jar MyApp 
----

----
Xxx xxxx foo(): xxx ns
Xxx xxxx bar(): xxx ns
Xxx xxxx baz(): xxx ns
MyApp java.lang.Object main(java.lang.Object): xxx ns
----

ぜひ試してフィードバックしてください!