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)

Sunday, December 18, 2011

メールからエンジニアの活動パターンを調査する in Groovy

これは Ariel Advent Calendar 2011 の記事です。

僕が以前所属していたアリエル・ネットワークではほとんどの情報がメールで共有されていました。面倒なミーティングに招待されたことや、誰かが typo まみれの恥ずかしいコードをコミットしたことも全てメールで届きます。それはログであり、誰が、何時、何をしているかがそこからわかります。

エンジニアの活動について知りたいならば最も適切な材料になるのは ITS (Issue Tracking System) から届くメールです。問題の報告、仕様の記述、進捗や調査内容の共有、質問と回答などなど、事あるごとにメールが飛びます。今回はそれらのメールの送信時間から何がわかるのか見てみたいと思います。使うのは手元に残っている 2007 - 2009 の12月のメールです。mbox で保存されている各月ごとのメールと次の Groovy スクリプトを使って、時間帯ごとの送信回数をグラフで表示してみます(DateヘッダがGMTだったので実際はJSTに変える処理が必要だったけどそこは省略):
def mbox = mboxName as File
mbox.withReader { r ->
    def acts = new int[24]
    while (r.ready()) {
        (r.readLine() =~ /^Date:.+\s(\d{2}):\d{2}:\d{2}\s/).with { m ->
            if (m) acts[m[0][1] as int]++ 
        }
    }
    acts.eachWithIndex { c, h ->
        println(String.format("%2d %s(%d)", h, '*'*(c/10), c))
    }
}

Dec 2007:
 
 0 **(27)
 1 **(20)
 2 (5)
 3 (5)
 4 (2)
 5 (0)
 6 (0)
 7 (0)
 8 (0)
 9 (1)
10 *************(132)
11 *********************************(337)
12 **************************************(384)
13 ***************(158)
14 ****************************(286)
15 *******************************(312)
16 *******************************(314)
17 *************************************(374)
18 ***********************************(351)
19 *******************************(315)
20 **********************(228)
21 ********************(203)
22 ****************(166)
23 *****(59)
 

Dec 2008:
 
 0 (5)
 1 (7)
 2 (0)
 3 (0)
 4 (1)
 5 (0)
 6 (0)
 7 (0)
 8 (0)
 9 *(17)
10 ***********(119)
11 ******************************(304)
12 ************************(245)
13 ***********(119)
14 ****************************(288)
15 *******************************(311)
16 *************************(250)
17 ***************************(275)
18 **********************(226)
19 *****************(171)
20 *********(99)
21 *****(55)
22 *******(77)
23 *(19)
 

Dec 2009:
 
 0 ******(68)
 1 **(26)
 2 **(29)
 3 **(21)
 4 (7)
 5 (8)
 6 (5)
 7 (2)
 8 (5)
 9 *******(75)
10 **********************************(349)
11 **************************************************************************(749)
12 ****************************************************************************(766)
13 **************************************************(502)
14 ***********************************************************(598)
15 *************************************************************(615)
16 ****************************************************************(642)
17 *********************************************************************(698)
18 ***************************************************************(634)
19 **********************************************************(584)
20 **********************************(343)
21 ***********************(235)
22 *************(136)
23 *************(139)
 

このグラフから次のことが見て取れます:

1. 10時 - 24時までが主な活動時間: 今は朝7時とかに出社するよくわからないグループもいるみたいですが。

2. 10時台になると一気に活発になる: 多くの人は 10時 - 11時に出社するようです。そう、アリエルは11時までに出社すればOKです。ギリギリに来る人が多かったですね。

3. ランチは13時台: 自分が早く出社してお腹が空いているからといって早めに誘ってしまうと、12時台のピークを奪うことになりますから止めましょう。

4. 午後の活動のピークは17時前後: ランチ後の睡眠欲に打ち勝ち(あるいは充足し)、ノッている時間帯です。この時間帯にミーティングが入ってしまうと生産性が非常に落ちます。

5. 20時 - 22時 にかけて人が帰宅していく: ただし、アリエルは15時以降ならいつでも帰ってよいことになってるし、帰りづらい雰囲気は皆無です。僕も明るいうちに会社出て遊びに行ったりしてました。逆に忙しい時は自宅で朝っぱらから夜中までとかもやってましたが。自由を制する力が求められます。

6. 2008年と比較すると2009年は倍増: 会社が急激に成長したようです。グラフの形があまり変わっていないのは仕事と一緒に人もどんどん増えた結果でしょうか。コアチームのメンバーはほとんど増えなかったですけど。

見落としていることもあるかもしれません。でも少なくともメールの送信時間だけでこれだけのことはわかるわけです。ここに人やissueの属性が加わればもっといろんなことがわかります。

このようにログを取っていてそれを利用できる会社なら、無駄な報告作業が減るかもしれませんね。

Tuesday, December 13, 2011

G5、それはGroovy使いの為のスライドショーツール

これは G* Advent Calendar の 12/13 (13日目) の記事です。

タイトルにあるとおり、 Groovy でスライドショーできるツール、 G5 をリリースします(おそらくクリスマスまでには!)。インスパイアされたのが HTML/JavaScript/CSS でスライドショーする S5Clojure でスライドショーする L5 なので、 G5 と名づけました。ちなみに L5 の作者の深町さん("はてな"の人)はアリエル時代の同僚で、S5 より L5 を先に知りました。このツール、作ろうかなと思ってから1年くらい経っていて、今回の Advent Calendar のネタとしてようやく手を付けたので、まだ数時間しか費やせてません。完成度は察してください。

まずはシンプルな例です:
new g5.SlideBuilder()({s{b("Look Ma, one line!")}})()

これで次のようにスライドショーが開始されます。今のところ Apache FOP 純正の AWT renderer 使っているので見た目が懐かしい感じですが、それはそれ。

ファイルに出力したい時はこうします:
new g5.SlideBuilder()({s{b("Look Ma, one line!")}})('application/pdf','one-line.pdf')

こんな仕上がり:

もう少しまともな例として G5 の紹介用スライドを G5 で書きました(途中まで)。こちらはまずは出来上がりから:


このスライドは次のコードで動いています:
import g5.SlideBuilder

def slideBuilder = new SlideBuilder()

def name = { 
    def attrs = [color:'#3283A1']
    slideBuilder.inline(it ? attrs + it : attrs, "G5") 
}
def title = { slideBuilder.block 'font-size':'48pt', 'margin-bottom':'18pt', it }
def properNoun = { slideBuilder.inline 'font-style':'italic', "\"$it\"" }

def slides = slideBuilder 'font-family':'Verdana', {
    slide {
        title { inline "Introduction to "; name('font-size':'64pt') }
        block "Nagai Masato" 
        block 'font-size':'16pt', color:'gray', {
            block "Twitter: @nagai_masato"
            block "Blog: http://nagaimasato.blogspot.com/"
        }
    }
    slide {
        title { inline "What is "; name(); inline "?" }
        list 'font-size':'24pt', {
            listItem { 
                image 'http://groovy.codehaus.org/images/groovy-logo-medium.png' 
                inline "Slideshow System" 
            }
            listItem {
                inline "Allows you to create and show slides using "; properNoun "Groovy"
            }
            listItem {
                inline "Uses "; properNoun "XSL-FO"; inline " and "
                properNoun "Apache FOP"; inline " for formatting slides"
            }
        }
    }    
} 
slides.show()

別名を使えば短く書けます。お好みでどうぞ。
import g5.SlideBuilder
 
def sb = new SlideBuilder()

def nm = { def attrs = [color:'#3283A1']; sb.i(it ? attrs + it : attrs, "G5") }
def tt = { sb.b 'font-size':'48pt', 'margin-bottom':'18pt', it }
def pn = { sb.i 'font-style':'italic', "\"$it\"" }

def ss = sb 'font-family':'Verdana', {
    s {
        tt { i "Introduction to "; nm('font-size':'64pt') }
        b "Nagai Masato" 
        b 'font-size':'16pt', color:'gray', {
            b "Twitter: @nagai_masato"
            b "Blog: http://nagaimasato.blogspot.com/"
        }
    }
    s {
        tt { i "What is "; nm(); i "?" }
        ls 'font-size':'24pt', {
            li { 
                im 'http://groovy.codehaus.org/images/groovy-logo-medium.png' 
                i "Slideshow System" 
            }
            li { i "Allows you to create and show slides using "; pn "Groovy" }
            li { 
                i "Uses "; pn "XSL-FO"; i " and "; pn "Apache FOP"
                i " for formatting slides" 
            }
        }
    }    
} 
ss()

G5 は こちら で公開しています。まだ開発ブランチにソースがあるだけですけど。Groovy 使いのみなさん、リリースされたら勉強会とかで使ってください ;)

時間がないので今日はこのへんで。

Wednesday, November 2, 2011

グリーに入社しました

11/1付けでグリーに入社しました。今後は当面グリープラットフォームの開発を担当します。

グリーでは相当なハードワークになりそうですが、いいきっかけなので"闘うプログラマー"を地で行ってみます。僕が入ったことでグリーに良い変化を与えられたらと思います。

これからもよろしくお願いします。

Sunday, October 23, 2011

アリエル・ネットワークを退職しました

私事ですが、先週でアリエル・ネットワーク(以下アリエル)を退職しました。正確には最終出社を終え、今はまだ有給消化期間です。

"美しいプログラムを書けるプログラマ"の言葉に共感し入社したのが2007年2月、そこから4年9ヶ月をアリエルで過ごしてきました。その間いろいろな変化がありました。20名だった社員は100名に増え、オフィスは2回移転、初期から関わってきた製品は成長して大きくなり、そして僕の立場はマネージャになっていました。

マネージャにならないかと打診されたのは1年ちょっと前でした。不安はありましたが、プロジェクトやチームを動かせる機会なんてそうそうないですし、エンジニアと兼務してもいいと言われたのでやらせてもらうことにしました。一応誤解のないように補足しておくと、アリエルではエンジニアの先にマネージャがあるわけではありません。いようと思えばずっとエンジニアでいれます。

マネージャになってからは、開発プロセスの改善や、チーム作りなど、望んでいたとおりそこでしか得られない貴重な体験ができました。しかしマネージメント上の課題や問題(半分くらいは開発とは直接関係のないこと)が頭の中を占める割合が増え、エンジニアの方に思うように力を入れらなくなると、果たしてこれが今自分がやりたいことなのだろうか、という疑問が出始めました。仕事だからと割り切り、生きがいを個人でのエンジニア活動に限定することもできなくなはいでしょうが、日々の大半は仕事です。僕はマネージャを辞め1エンジニアとして再出発することを決断しました。

再出発には別の道を進むことを条件に付け加えました。"不得手なことをする"と言い換えてもいいです。入社以来1つの製品をひたすら開発し続ける中、得意とするフィールドができあがり、そこでしか勝負しない自分に嫌気がさしていました。その思いはプロジェクトに余裕がない状態でマネージャが考えることではない、と自分に言い訳しながら無視してきましましたが、こうなればそんな言い訳はできません。

当初、アリエルに残る道も考えましたが、選んだのは退職でした。しがらみを乗り越えるより、不便になること覚悟で何もかも捨てて身軽になる方に魅力を感じました。とにかくシンプルにしたかった。

アリエルではほんとにいろんな人にお世話になりました。特に開発の3アミーゴスである、井上さん大谷さん中山さん、そしてチームの皆には最後まで迷惑をかけ、いろいろと助けてもらいました。この場を借りて改めて感謝の言葉を伝えてたいと思います。本当にありがとうございました。

11月からは新しい場所で出直しです。これからもよろしくお願いします。

Thursday, October 13, 2011

Groovy の AST を楽に書く方法

AST を書くのは大変

Groovy は AST 変換の実装用に AST を生成する DSL を提供しています。ところがこの DSL は複雑で習得が困難です。次のような GString の AST を生成したいとします:
"Hi, $name."
その場合、DSL はこうなります。こんなもの構文と同じ数覚えられません:
gString 'Hi, $name.', {
    strings {
        constant 'Hi, '
        constant '.'
    }
    values {
        variable 'name'
    }
}

ソースコードから直接生成することもできますが、コンパイラによるチェックの恩恵を受けられないですし (これについては Joachim Baumann が説明しています)、DSL よりパフォーマンスが悪いです。次のベンチマークを見てください:
@Grab('com.googlecode.gbench:gbench:0.2.2')
import gbench.BenchmarkBuilder
import org.codehaus.groovy.ast.builder.AstBuilder

def benchmarks = new BenchmarkBuilder().run {
    'DSL to AST' {
        new AstBuilder().buildFromSpec {
            gString 'Hi, $name.', {
                strings {
                    constant 'Hi, '
                    constant '.'
                }
                values {
                    variable 'name'
                }
            }
        }
    }
    'Code to AST' {
        new AstBuilder().buildFromString('"Hi, $name"')
    }
}
benchmarks.prettyPrint()

            user system cpu    real

DSL to AST     0      0   0  339918
Code to AST    0      0   0 2076590
というわけで、DSL を実装コードやテストコードを見ながら書く羽目になるのですが、これが大変な作業になることは簡単に想像してもらえるはずです。

どうすれば楽に書けるのか

この問題を解決する為に DSL をコードから自動生成してくれるライブラリを作りました。 AstSpecBuilder と名付けたこのライブラリはここで公開しています。使い方はとても簡単、build メソッドに馴染みのあるコードを文字列で渡すだけです:
import astspecbuilder.*

def spec = new AstSpecBuilder().build('"Hi, $name."')

def expectedSpec =  '''\
block {
    returnStatement {
        gString 'Hi, $name.', {
            strings {
                constant 'Hi, '
                constant '.'
            }
            values {
                variable 'name'
            }
        }
    }
}
'''
assert expectedSpec == spec
AST からも生成できます。というより実は上のメソッドは次のコードのショートカットに過ぎません:
import astspecbuilder.*

def ast = new AstBuilder.buildFromString('"Hi, $name."')
def spec = new AstSpecBuilder().build(ast)

インデントはデフォルトではスペース4つですが、変更するオプションが付いてます。次のようにすると DSL がタブ1つでインデントされます:
def spec = new AstSpecBuilder(indent: '\t').build('"foo"')

def expectedSpec = '''\
block {
\treturnStatement {
\t\tconstant 'foo'
\t}
}
'''
assert expectedSpec == spec