Saturday, May 28, 2011

@Benchmark annotation for Groovyをリリース!

@Benchmarkアノテーションはメソッドの実行時間をコードの追加なしで計測できるようにしてくれます。次の例は@Benchmarkの基本的な使用例です:
----
@Benchmark
def method() {
    // an operation takes 1 ms
}

method()
----
この場合標準出力に"1000000"が出力されます. @Benchmarkは時間をナノ秒で表します。

出力形式に手を加える為のオプションが2つあります:
----
@Benchmark(prefix = 'execution time of method(): ', suffix = ' ns')
def method() {
    // an operation takes 1 ms
}

method()
----
こうすると出力は"execution time of method(): 1000000 ns"となります。

@Benchmarkは出力にthis.println(value)を使います。なので目的に沿ったwriterを"out"プロパティにセットすることで出力をリダイレクトできます:
----
def results = new StringBuffer()
this.setProperty("out", new StringBufferWriter(results))

// method calls

this.setProperty("out", null)

results.toString().eachLine { result ->
    println "${result.asType(int.class) / 1000000} ms"
}
----

ダウンロードはこちらから。

Sunday, May 22, 2011

Exploring Groovy 1.8 Part 1 - そのコード、本当に最適化されてますか?

Groovy目下の課題であるパフォーマンス向上の為、1.8では整数演算とメソッド直接呼び出しへの最適化が行われるようになりました。どちらの最適化も考え方は同じで、簡単にいってしまえば"出来る限り書かれた通りにコンパイルする"というものです。これはこれまでGroovyコンパイラが構文を正しく解析してくれなかった、という意味ではありません。Groovyでは柔軟なメタプログラミングを可能にする為にコンパイル時に劇的な変換が施されるのです(このあたりは以前のポスト、インサイドGDKでも触れています)。問題は例外がなかったことです。この後の話の都合上、フィボナッチ数の計算メソッドを例としてあげます:
----
int fib(int n) {
    if (n < 2) return n
    return fib(n - 1) + fib(n - 2)
}
----


このコードは全くメタプログラミングの恩恵を受ける必要のないコードですが、v1.7以前では次のように変換されます(実際はGroovyのAPIが登場しますし行っていることももう少し複雑なのですが、今回の話では重要ではない為省略します)。全ての演算/メソッド呼び出しはリフレクションへ置き換えられ、整数に対してせわしなくボクシングとアンボクシングが繰り返されます:
----
int fib(int n) {
    // Get the Method objects
    if (lessThanMethod.invoke(Integer.valueOf(n), Integer.valueOf(2))) 
        return Integer.valueOf(n).intValue();
    return 
        ((Integer) plusMethod.invoke(
            fibMethod.invoke(
                minusMethod.invoke(Integer.valueOf(n), Integer.valueOf(1)
            ),
            fibMethod.invoke(
                minusMethod.invoke(Integer.valueOf(n), Integer.valueOf(2)
            )
        )).intValue();
    );
----


これがv1.8では次のように最適化されます。これが冒頭での言った"出来る限り書かれた通りにコンパイルする"の意味です:
----
int fib(int n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
}
----


さて、話はこれで終わりません。というのも、どうやらこの最適化の恩恵を受ける条件は我々が想像しているよりずっと厳しいものだということです。リリースノートに書かれている次の注意事項を忠実に守るだけでは充分ではありません:
1. 整数の最適化を求めるのであれば1つの式の中でint型とInteger型を混ぜないこと
2. メソッド呼び出しの最適化を求めるのであれば"this"上で実行でき、引数の型がprimitiveかfinalであること


次の表を見てください。これはフィボナッチ数を求めるメソッドをいくつかの方法で実装し、それらの実行速度をv1.8.0とv1.7.10で比較したものです:
フィボナッチ(30)の実行時間(単位はナノ秒)
ランク
アルゴリズム / スタイル
v1.8
v1.7
改善
1
反復 + Groovyスタイル for loop
49720
46276
-6.93%
Down :-(
2
反復 + Java/Cスタイル for loop
129360
77824
-39.84%
Down :-(
3
再帰 + if + return
45080820
548522821
+1116.75%
Up :-)
4
再帰+ 三項演算子
120489259
538476119
+346.91%
Up :-)
5
再帰 + if-else
259945537
546381516
+110.19%
Up :-)

この結果は次の3つのことを示しています:
1. 反復版はパフォーマンスが少し劣化した
2. 再帰版はパフォーマンスが大幅に向上した
3. コーディングスタイルによるパフォーマンスの差が広がった

理由は簡単です。結果が芳しくないものは両方、あるいはいずれかの最適化に失敗しているのです。この表で言えば、先に例にあげた再帰 + if + return版以外は全て何らかの形で最適化に失敗しています。例えば再帰を使った中で最も悪い結果となった再帰 + if  + else版はこうです:

----
int fib(int n) {
    if (n < 2) n
    else fib(n - 1) + fib(n - 2)
}
----

これは次のように変換されます(繰り返しになりますが正確には実際のものとは違います):
----
int fib(int n) {
    if (n < 2) return n;
    else return 
        ((Integer) plusMethod.invoke(
            fibMethod.invoke(Integer.valueOf(n - 1)),
            fibMethod.invoke(Integer.valueOf(n - 2))
        )).intValue();
    );
}
----

たったこれだけのスタイルの違いが最適化が成功するかを左右し、パフォーマンスに5倍の差を作るのです。こういった問題はこれから改善されていくでしょうが、少なくとも現時点では諸手を上げて喜べる段階ではないようです。


さてさて、そのコード、本当に最適化されてますか?




(今回計測に使ったコード一覧)


反復 + Groovyスタイル for loop:
----
int fib(int n) {

    int a = 0
    int b = 1
    int c
    for (int i in 2..n) {
        c = a + b
        a = b
        b = c
    }
    b
}
----


反復 + Java/Cスタイル for loop:
----
int fib(int n) {
    int a = 0
    int b = 1
    int c
    for (int i = 2; i <= n; i++) {
        c = a + b
        a = b
        b = c        
    }
    b
}
----


再帰 + if + return:
----

int fib(int n) {
    if (n < 2) return n
    return fib(n - 1) + fib(n - 2)
}
----


再帰 + 三項演算子:
----
int fib(int n) {
    n < 2 ? n : fib(n - 1) + fib(n - 2)  
}
----


再帰 + if-else:
----
int fib(int n) {
    if (n < 2) n
    else fib(n - 1) + fib(n - 2)
}
----

Wednesday, May 4, 2011

Gjが更新されました!

"Gj"を今日更新しました。"Gj"は素早く、簡単に、そしてなんの苦労もいらずにGroovyプログラムを配布形式にする為のスクリプトです。 (以前のポストも見てください).

更新内容は以下です:

1. Groovyをバンドルするオプション
-bg/--bundlegroovy オプションが追加されました。Groovyのライブラリを直接指定する代わりにこのオプションを使うことができます。
----
> cat src\PrintHello.groovy
println 'hello'

> groovy Gj.groovy -bg -s src -m PrintHello printhello.jar

> java -jar printhello.jar
hello
----

2. javac用のデフォルトのjavaバージョン
javac用のデフォルトのjavaバージョンがシステムと同じバージョンになるよう変更されています。

3. Gjのバージョンを出力するオプション
-ver/--version オプションが追加されました。このオプションを使ってGjのバージョンを確認することができます。
----
> groovy Gj.groovy -ver
Gj version: 11.05.05
----

新しいバージョンはこちらからダウンロードできます。使ってみてください ;-)

Monday, May 2, 2011

Poison for Groovy

昨夜、Gjスクリプトのテスト中にgroovy.batのバグにぶち当たりました (Gjスクリプトは実行可能なjarファイルを簡単に作るために僕が書いたものです。詳しくは以前のポストを見てください)。groovy.batが * (アスタリスク)を含んだコマンドライン引数を扱えないのです。

例えば、次のコマンドを入力するとgroovy.batは応答しなくなります。
----
groovy Foo.groovy "*"
----
あるいは
----
groovy -e "println '*'"
----

GroovyMainに直接渡した時は動作します。
----
java -cp "%GROOVY_HOME%\embeddable\groovy-all-1.8.0.jar" groovy.ui.GroovyMain -e "println '*'"
*
----

既知バグかどうか探したら、GROOVY-3043を見つけました。幸運なことにこのバグは2年前に修正されていました。よしよし、そういうことなら... って修正済み? いやちょっと、まだバグってますよ。

奇妙なことに修正済みの (不確か、とりあえず動いてるように見える) startGroovy.bat (groovy.batから呼ばれ、コマンドライン引数をパースする)はGROOVY-3043に添付されています。なぜこれがいまだに提供されていないのかはわかりません。

このバグをGROOVY-4805に新しい問題として報告しました。僕が書いたパッチも提供したのでWindows NT系のユーザの方はそれを当てるのもよいと思います。いずれにせよ、できるだけ早くこのバグが修正されることを望みます。

Friday, April 8, 2011

Gj、それはGroovyプログラムを配布する最も簡単な方法

Groovy向けのビルドツールはあります。たぶん素晴らしいツールなんだろうと思います。 でも僕はめんどくさがりやです。たかがjarファイルを作る為だけに大きなツールを覚えたくはありません。

そこで1つのGroovyスクリプトを書きました。名前は'Gj'です。Gjを使えば、簡単に痛みもなくjarファイルを作れます。1つ最も簡単なGjの使用例を見せます。あなたがFoo.groovyという名前のGroovyのソースファイルを、srcdirディレクトリの下に持っていて、それでfoo.jarという実行可能なjarファイルを作りたいとします。その場合、こう入力します:

> groovy Gj.groovy -s srcdir -m Foo foo.jar

たくさんのライブラリを使っている? 問題ありません。引数にそれらのディレクトリを追加すればいいだけです。Gjがそれらを結果のjarファイルにマージします。

> groovy Gj.groovy -s srcdir -m Foo -l libdir1,libdir2,libdir3

もっとヘルプが欲しいということであれば、こちらからGjをダウンロードしてこう入力してください:

> groovy Gj.groovy -h

Monday, February 28, 2011

Groovy Monkeyでより良いEclipseライフを

あなたがEclipseのヘビーユーザなら、Groovy Monkeyを飼うことをお勧めします。この好奇心旺盛なおさるはあなたのEclipseへ機能を簡単に追加する方法を提供してくれます。

まずはおさるをアップデートサイトから手に入れましょう。私の知る限りペットは高いものですが、ラッキーなことにこのおさるは無料です :-)

おさるを手に入れましたか? あなたのEclipseのメニューバーに現れましたか? では彼を押してGroovy Monkeyチームが提供するサンプルプロジェクトを作りましょう。 このプロジェクトにはGroovy Monkeyを理解するための良い例が入っています。







その中からもっとも単純な例を取り上げましょう:
----
/*
 * Menu: Open Dialog > Groovy
 * Script-Path: /GroovyMonkeyScripts/monkey/OpenDialog_Groovy.gm
 * Kudos: ervinja
 * License: EPL 1.0
 * Job: UIJob
 * DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
 */

out.println( 'hello world from Groovy' )
org.eclipse.jface.dialogs.MessageDialog.openInformation( window.getShell(),
                                                         'Monkey Dialog',
                                                         'Hello World from Groovy' )
----
とても単純でしょう?

Groovy Monkeyはヘッダにメタデータを持ちます。この例で重要なメタデータはMenuとJobです。Menuはこのスクリプトを実行するメニューアイテムの位置を表します。こんな風に:














Jobは実行するスレッドを表します。 JobにはJob、UIJob、WorkspaceJobの3つがあり、これらはEclipse Jobs APIに定義されています(詳細はこちらを参照してください)。 この例ではUIにアクセスする為UIJobが指定されています。

"out"と"window"がどこから来たのか不思議ですか? これらはDOM (Domain Object Model) と呼ばれる予め定義されているオブジェクトです。"out"はConsole DOMと呼ばれるorg.eclipse.ui.console.MessageConsoleStreamのオブジェクトでGroovy Monkeyが利用するEclipseコンソールにアクセスする為のものです。"window"はWindow DOMと呼ばれるorg.eclipse.ui.IWorkbenchWindowのオブジェクトでアクティブなEclipseウィンドウへアクセスする為のものです。DOMがなければ、この例は次のようになるでしょう:
----
import org.eclipse.ui.*
import org.eclipse.ui.console.*

def console = new MessageConsole('Groovy Monkey', null)
ConsolePlugin.default.consoleManager.with {
addConsoles([console] as IConsole[])
showConsoleView(console)
}
def out = console.newMessageStream()
def window = PlatformUI.workbench.activeWorkbenchWindow

out.println( 'hello world from Groovy' )
org.eclipse.jface.dialogs.MessageDialog.openInformation( window.getShell(),
                                                         'Monkey Dialog',
                                                         'Hello World from Groovy' )
----

Groovy Monkeyは他にもDOMを提供します。 これらはOutline ViewかInstalled DOMs Viewで確認できます。

もう1つ、私が書いた例を紹介しましょう。次の例はファイルのダウンロード機能を追加するもので、Includeメタデータを使ってGroovy Monkeyスクリプトからワークスペースのリソースを使う方法を示しています。この例を試すにはURLConnectionBuilderを取得する必要があります。

----
/*
 * Menu: Download File...
 * Script-Path: /GroovyMonkeyScripts/monkey/DownloadFile.gm
 * Kudos: Nagai Masato
 * License: EPL 1.0
 * Job: UIJob
 * Include: /GroovyMonkeyScripts/toybox/urlconnbuilder-0.0.2.jar
 */

import net.sourceforge.urlconnbuilder.*

def download = { url, filename ->
new URLConnectionBuilder().url(url) {
       connect {
           configure(requestProperties: ['User-Agent': 'George'])
           communicate(input: { conn, stream ->
               new File(filename).newOutputStream() << stream
           })
       }
}
}

shell = jface.shell(text: 'Download File') {
    def urlTxf, filenameTxf
    gridLayout(numColumns: 2)
    label(text: 'From:')
    urlTxf = text() {
        gridData(widthHint: 300)
    }
    label(text: 'To:')
    filenameTxf = text() {
        gridData(widthHint: 300)
    }
    button(text: 'Download') {
        gridData(
            horizontalSpan: 2,
            horizontalAlignment: org.eclipse.swt.layout.GridData.END
        )
        onEvent(type: 'Selection', closure: {
            download(urlTxf.text, filenameTxf.text)
            shell.close()
        })
    }
}
shell.pack()
shell.open()
----

Saturday, February 5, 2011

Groovy URLConnectionBuilder 0.0.2 が出ました

URLConnectionBuilder 0.0.2 をリリースしました。URLConnectionBuilderは初めてという方は先に以前の投稿を見てください。以下が変更点です。


1. パッケージ名の変更
新しいパッケージ名はnet.sourceforge.urlconnbuilderです。


2. プロキシへのより良いサポート
----
URLConnectionBuilder connBuilder = new URLConnectionBuilder()
connBuilder.url('http://groovy.codehaus.org/') {
    connect(proxy: 'http://proxyhost:proxyport') {
        configure(
            // configure URLConnection
        ) 
        communicate(
            input: { conn, stream ->
                println stream.text
            }
        )
    }
}
----


URL版:
----
proxy: new URL('http://proxyhost:proxyport')
----


URI版:
----
proxy: URI.create('socks5://proxyhost:proxyport')
----


3. 認証へのサポート
----
URLConnectionBuilder connBuilder = new URLConnectionBuilder()
connBuilder.url('http://groovy.codehaus.org/') {
    connect(authentication: 'username:password') {
        configure(
            // configure URLConnection
        ) 
        communicate(
            input: { conn, stream ->
                println stream.text
            }
        )
    }
}
----


List版:
----
authentication: [ 'username', 'password' ]
----


Map版:
----
authentication: [ username: 'username', password: 'password' ]
----