Java8のInvokeDynamic実装一新によるGroovyのindyモードの性能改善効果が凄すぎた件

本日、Java Day Tokyo 2014に来ています。

で、ついさっきのセッションで「JDK8ではInvokeDynamic(以下、indy)の実装を一新したのですごく速くなったよ」という話を聞いたので、Groovyのindyモードで試してみました。

Groovyは2.0(現在は2.3)でindy対応されています。 それ以前はGroovy独自機構による性能改善が行われていました。 しかし、JDK7からのホヤホヤなindyを前提にしてしまうと、JDK6で動かなくなってしまいます。 Groovyは下位互換性が重視されているので、これは問題です。 というわけで、indyが使えないJDK上でも使えるように既存の独自機構もそのまま残っているし、indy不要バージョンが標準になっていて、indyオプションを利用可能にするには$GROOVY_HOME/lib配下を$GROOVY_HOME/indy配下にあるファイルで上書き置換してあげないといけません。

と言うぐらいに色々面倒くさいのですが、じゃあそこまでやってindyを使ったところでどれだけ嬉しいのかというと、JDK7までは「全然うれしくない」が答えでした。 JDK7のindy実装がこなれてない&Groovyのindy対応もカリカリではないので、全然性能がでなかったんですね。 独自機構の方が全然速かったわけです。

さて、「JDK8ではInvokeDynamic(以下、indy)の実装を一新したのですごく速くなったよ」という話を受けて試してみたんですが、indyモード&JDK8すごくヤバいことになってました。

サンプルコード

コードとしてはフィボナッチ数の算出です。 ローカルにあったファイルを発掘したもなので、実装が云々というツッコミはこの際受け付けません。

class Fibonacci {
    def calc = { n ->
      return (n == 0) ?  0 :
             (n == 1) ?  1 :
             call(n - 1) + call(n - 2)
    }
 
    static void main(String[] args) {
        def num = args[0] as int
        println "Fib($num) = ${new Fibonacci().calc(num)}"
    }
}

これをJDKの7u55と8u05で実行してtimeコマンドで計ってみました。 厳密なベンチではないので参考ぐらいのやさしい気持ちで読んでください。 ちなみにGroovyは最新ホヤホヤの2.3.0です。

Groovy 2.3.0 (indy有効) + JDK 7u55

$ java -version
java version "1.7.0_55"
Java(TM) SE Runtime Environment (build 1.7.0_55-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.55-b03, mixed mode)

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  42.03s user 0.35s system 102% cpu 41.538 total

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  33.75s user 0.34s system 102% cpu 33.197 total

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  41.01s user 0.35s system 102% cpu 40.512 total

Groovy 2.3.0 (indy有効) + JDK 8u05

$ java -version
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  20.04s user 0.29s system 108% cpu 18.777 total

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  19.73s user 0.29s system 108% cpu 18.520 total

$ time groovy --indy Fibonacci.groovy 40
Fib(40) = 102334155
groovy --indy Fibonacci.groovy 40  20.27s user 0.31s system 107% cpu 19.076 total

実に2倍。これはヤバい。

(参考)Groovy 2.3.0 (indy無効=Groovy独自機構) + JDK 8u05

参考までにindyを使わないGroovy独自機構による性能も見てみましょう。

$ java -version
java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

$ time groovy Fibonacci.groovy 40
Fib(40) = 102334155
groovy Fibonacci.groovy 40  25.60s user 0.30s system 106% cpu 24.407 total

$ time groovy Fibonacci.groovy 40
Fib(40) = 102334155
groovy Fibonacci.groovy 40  23.40s user 0.28s system 107% cpu 22.031 total

$ time groovy Fibonacci.groovy 40
Fib(40) = 102334155
groovy Fibonacci.groovy 40  23.68s user 0.28s system 107% cpu 22.288 total

なんと、JDK7+indyにはダブルスコアで勝ってたのに、JDK8+indyに追い越されてしまいました。

indy全盛時代の幕開けを感じますね。

というようなことを、Java-ja枠のTDDセッションを聞きながらまとめてみました。