はじめに
以前下記のブログで各種JVM言語で実行速度を比較している記事を見つけました。
【Java 3兄弟で】Groovy, Scala, Clojure【フィボナッチ】
自分の環境でもGroovyで同じく検証すると記事よりかなり速かったので、よく比べてみると自分のソースでは型指定をintとして書いていました。
そのことをブログのコメントで書くと管理人の方も比較してくださり同じく速度に違いが出ることが確認できました。
一般的にGroovyの入門記事などと読むとGroovyは基本ラッパー型しか使わずに、数値計算などもGroovy側でよしなに変換してくれるから気にしなくていいといったことしか書かれていないため今まで考えたこともありませんでした。
そこでGroovyで型指定した時にどういった挙動になるか調べてみました。
やったこと
テストコード
次のようなコードを書いてみました。
def num0 = 0
Integer num1 = 1
int num2 = 2
println "Integer: ${Integer.class}"
println "int: ${int.class}"
println()
println "num0: ${num0.class}"
println "num1: ${num1.class}"
println "num2: ${num2.class}"
defとIntegerとintで変数を宣言し、それぞれ整数リテラルで初期化しました。
その後Integer、intそれぞれのクラス名と変数のクラス名を取得してみました。
実行結果
結果は次のように出力されました。
Integer: class java.lang.Integer
int: int
num0: class java.lang.Integer
num1: class java.lang.Integer
num2: class java.lang.Integer
Javaではintはクラスではないので2行目はエラーになると思ったのですが「int」と表示されました。
Groovyではクラスのように見えるようです。
変数のクラス名はすべてIntegerになっています。3個目はintになるのかなと思いましたがこれもIntegerです。Groovyでは整数リテラルはIntegerになるためでしょうか
テストコード2
次に書きのようなコードを試しました。
def num0 = null
Integer num1 = null
int num2 = null
intがクラスのように見えるのならこのコードは受け付けるように思えます。
実行結果
Exception thrown
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'null' with class 'null' to class 'int'. Try 'java.lang.Integer' instead
at ConsoleScript23.run(ConsoleScript23:3)
キャストできない例外を吐いて、intじゃnull使えないからInteger使えと怒られてしまいました。
やはり内部的には普通にプリミティブintのようです。
ちなみに下記のように記述すると2行目でNullPointerExceptionが出てきます。
Integer num1 = null
int num2 = num1
速度比較
参考ブログと同様にフィボナッチ数列を最適化無しでintとInteger版2つ作って速度を比較してみました。
int fib1(int num) {
if(num <2) return num
fib1(num-1) + fib1(num-2)
}
Integer fib2(Integer num) {
if(num <2) return num
fib2(num-1) + fib2(num-2)
}
def before,after
before = System.currentTimeMillis()
println fib1(39)
after = System.currentTimeMillis()
println "${after-before}ms"
before = System.currentTimeMillis()
println fib2(39)
after = System.currentTimeMillis()
println "${after-before}ms"
63245986
1600ms
63245986
8357ms
5倍くらい違います。
やっぱりintにしたほうが速いようです。
結論
このようにGroovyにおいてもintとIntegerの挙動は異なり、場合によってはintで宣言したほうが速度が改善されることがわかりました。
しかしプリミティブ型を使用するとJavaと同様にnullが使用できず、紛れ込んでしまった時例外が発生するので注意が必要です。
また再帰のような例ですと、前回説明したメモ化のようなテクニックを使用したほうが安全でしょう。
参考:メモ化により再帰を高速化する
そもそも計算に速度を求める用途でGroovyが向いているとも思えません。
どうしても一部分で速度を求めたい以外でGroovyらしいコーディングから外れるのはやめたほうがいいでしょう。
ちなみにScalaは
Scalaは殆ど触ったことがなく、Groovyと比較で調べてみた程度なのですがコンパイル型なのもあり速いようです。
特にScalaはプリミティブ型がなくすべてオブジェクト型なのにJavaと遜色なく使えると聞いて、なんでなんだろうと思って調べてみました。
どうもScalaはコード上ではオブジェクトとして表現されていても、コンパイル時に最適化できるときは積極的にプリミティブに変換しているようです。
なのでフィボナッチ数列のような例だと速く処理できて、かつプログラマはそれを意識しなくて良いのです。
なかなかいい機能ですね(その代償なのかコンパイル速度は遅いらしいですが)
最後に
あまり言語機能に詳しくはないため間違いや誤解などあるかもしれません。
何かご指摘等ありましたらコメントお願いします。