はじめに
こんにちは、@rs_tukkiです。
これはJava Advent Calender 2023の18日目の記事です。
繰り返し処理と言えば
Javaで繰り返しと言えばいの一番に習うのはfor構文ですが、
Java8からは新たな繰り返しや集約処理の構文としてStream
がサポートされています。
これは処理の流れを宣言的に記述できる構文で、
たとえば、
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
といった繰り返し処理は、
Stream.of(1, 2, 3, 4, 5).forEach(System.out::println);
といったように関数の繋ぎ合わせで記述することができます。
…ところで、このfor文とStream文って、どちらが速いんでしょうか。
感覚的には後から出てきたStreamの方が速そうですが、まずはGPTに相談してみました。
基本的にはデータ量が小さいほどfor文の方がパフォーマンスが向上する、とのこと。
本当にそうなのか、ちょっと検証してみることにします。
どちらが速いのか
という訳で、書いたコードがこちら。
ちなみにJavaのバージョンは17.0.4
です。
public static void main(String[] args) {
final List<Integer> dataSet = new ArrayList<>();
for (int i = 1; i <= 100000000; i++) {
dataSet.add(i);
}
System.out.println("開始:for");
long startTime = System.currentTimeMillis();
long sumFor = 0;
for (Integer data : dataSet) {
if (data % 3 == 0) {
sumFor += data;
}
}
System.out.println(sumFor);
long endTime = System.currentTimeMillis();
System.out.println("終了:for");
System.out.println("処理時間:" + (endTime - startTime) + "ms");
System.out.println("===============");
System.out.println("開始:stream");
startTime = System.currentTimeMillis();
final long sumStream = dataSet.parallelStream()
.filter(entry -> entry % 3 == 0)
.mapToLong(entry -> entry)
.sum();
System.out.println(sumStream);
endTime = System.currentTimeMillis();
System.out.println("終了:stream");
System.out.println("処理時間:" + (endTime - startTime) + "ms");
}
めちゃくちゃ単純ですが、やっていることは
- 1から1億までの数値からなるListを用意し
- List内の3の倍数だけを足し合わせて最後に出力する
- forとStreamでそれぞれ行い実行時間を計測する
というプログラムです。
本当はもう少し大きい数でやりたかったのですが、int型の最大値までデータセットを用意するとOOMになってしまうので断念しました...
実行結果
そしてこちらのコードを実行してみると、以下のような結果になりました。
開始:for
1666666683333333
終了:for
処理時間:729ms
===============
開始:stream
1666666683333333
終了:stream
処理時間:504ms
何回か実行してみましたが、平均してStreamの方が1.4倍前後速くなっています。
GPTによるとデータ量によってはforの方が速くなるとのことなので、データセットをもう少し減らして再度実行してみると、
5000万
開始:for
416666658333333
終了:for
処理時間:246ms
===============
開始:stream
416666658333333
終了:stream
処理時間:159ms
5000万ではまだStreamが優位でしたが、
1000万
開始:for
16666668333333
終了:for
処理時間:80ms
===============
開始:stream
16666668333333
終了:stream
処理時間:87ms
1000万ではforが逆転しました。
とはいえここまで来るとほぼ差はなさそうなので、コード上でどちらを採用するか迷った場合
- 巨大なデータセットを扱う処理はStream
- データセットがそこまで多くない場合、実際に実行時間を計測して
- Streamの方が速い場合はStream
- forの方が速い場合、(数msの差が気にならないなら)可読性で判断
とするのがよさそうです。
まとめ
今回はforとStreamの実行時間を、実際にコードを書いて比較してみました。
最終的には場合によるという何とも言えない結果になりましたが、だからこそ何も考えずこちらと決めつけるのではなく、都度そのコードに対してどちらで書くのがよいかを考えるようにしてきましょう。