日記 (2016 年 11 月 9 日)

プログラムでループを回すというのはよくやると思うんですが、 Groovy ではループの方法が主に for 文と each メソッドの 2 通りあります。 速度にどのくらいの違いがあるのか気になったので調べてみました。

まずは Java でもよくやる由緒正しき for 文です。 ついでに、 ループ回数を保持する変数がプリミティブ (int) である場合とラッパークラス (Integer) である場合も比較しておきます。 Groovy では、 プリミティブで宣言しても内部的には全てラッパークラスで扱われるらしいんですが、 どう違いが出てくるんでしょうか。

// 通常 for 文 (プリミティブ版)
for (int i = 0 ; i < number ; i ++) {
}
// 通常 for 文 (ラッパークラス版)
for (Integer i = 0 ; i < number ; i ++) {
}

次は拡張 for 文に Range オブジェクトを渡してみます。 Groovy でもそのままの構文で使えます。

// 拡張 for 文 (プリミティブ版)
for (int i : 0 ..< number) {
}
// 拡張 for 文 (ラッパークラス版)
for (Integer i : 0 ..< number) {
}

最後は each メソッドです。

// each メソッド (プリミティブ版)
(0 ..< number).each() { int i ->
}
// each メソッド (ラッパークラス版)
(0 ..< number).each() { Integer i ->
}

繰り返し回数の number を 1000 万にして試した結果が以下になります。

タイプ所要時間
通常 for 文 (プリミティブ版)5 ms
通常 for 文 (ラッパークラス版)78 ms
拡張 for 文 (プリミティブ版)20 ms
拡張 for 文 (ラッパークラス版)20 ms
each メソッド (プリミティブ版)577 ms
each メソッド (ラッパークラス版)486 ms

ということで、 一番速いのは由緒正しき普通の for 文を普通にプリミティブ型で使った場合らしいです。 同じ普通の for 文でも、 ループ変数をラッパークラス型にするとかなり遅くなるのは、 i ++ の実行がラッパークラスになってる分だけ遅くなってるからだと思います。 次に速いのは拡張 for 文で、 これについてはプリミティブ型でもラッパークラス型でも速度の違いがあまり出ませんでした。 一番遅いのは each メソッドで、 ダントツで遅いです。 ただ、 同じ each メソッドでも、 こちらはラッパークラスを使った方が速いようです。 おそらく、 0 ..< numberRange オブジェクトを作るときに、 内部的に Integer として数値を扱っているため、 プリミティブ型で受け取ろうとするとアンボクシング処理が入るからでしょうか。

ちなみに、 静的コンパイルにしても特に結果は変わりませんでした。 メソッド呼び出しとかがこれといってないので、 当然といえば当然ですが。

ということで、 Groovy でループを回したければ普通に for 文を使うのが一番速いということでした。 せっかく Groovy なんだし each とか使っていきたいところですが、 遅いのが難点ですね