Java言語でたびたび比較される for文 と Stream ですが、実際のところどちらが速いのでしょうか?
結論から申し上げますと、小規模~中規模のデータセットでループ処理を使うなら、断然 for文 が速いようです。
しかし、大規模なデータセットなどでは、Stream の方が速いこともあるみたいなので、その辺りのことを深掘りしていきたいと思います。
広告
for文の利点とStreamの欠点
for文の利点
for文 は下記の理由から、小規模~中規模のデータセットでは Stream よりループ処理が速いようです。
- 低レベルで直接アクセス(配列やListのget(i)など)
- オーバーヘッドが最小(ループカウンタと条件分岐のみ)
- JITコンパイラが最適化しやすい
Streamの欠点
Stream の下記の理由から、小規模~中規模のデータセットでは for文 よりループ処理が遅いようです。
- 内部イテレータを使い、ラムダ式やメソッド参照で関数呼び出しが発生
- プリミティブ型ボックス化/アンボックス化のコスト
- インターフェース呼び出しのオーバーヘッド
- 小規模データでは初期化コストが目立つ
大規模のデータセット(100万件)で比較
大規模のデータセットで比較したソースコードは下記の通りです。
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class Benchmark {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) list.add(i);
// for文(インデックス)
long start = System.nanoTime();
long sum1 = 0;
for (int i = 0; i < list.size(); i++) {
sum1 += list.get(i);
}
long time1 = System.nanoTime() - start;
System.out.println("for (index): " + time1 / 1_000_000.0 + " ms, sum=" + sum1);
// for-each
start = System.nanoTime();
long sum2 = 0;
for (int x : list) {
sum2 += x;
}
long time2 = System.nanoTime() - start;
System.out.println("for-each: " + time2 / 1_000_000.0 + " ms, sum=" + sum2);
// Stream sequential
start = System.nanoTime();
long sum3 = list.stream().mapToLong(Integer::longValue).sum();
long time3 = System.nanoTime() - start;
System.out.println("Stream seq: " + time3 / 1_000_000.0 + " ms, sum=" + sum3);
// parallelStream
start = System.nanoTime();
long sum4 = list.parallelStream().mapToLong(Integer::longValue).sum();
long time4 = System.nanoTime() - start;
System.out.println("parallelStream: " + time4 / 1_000_000.0 + " ms, sum=" + sum4);
}
}
結果
for (index): 2.5 ms, sum=499999500000
for-each: 1.8 ms, sum=499999500000
Stream seq: 12.3 ms, sum=499999500000 ← 約5-7倍遅い
parallelStream: 1.2 ms, sum=499999500000 ← 大規模で速くなる
まとめ
ここまでの結果を表にまとめると次の通りになります。
| 状況 | 推奨 | 理由 |
|---|---|---|
| パフォーマンス最優先(ホットパス) | for文 | 速い。可読性は犠牲 |
| 可読性/簡潔さ優先(小規模、非ホットパス) | Stream | コードが短く、メンテナンスしやすい |
| 大規模並列処理 | parallelStream | 自動並列化。ただし、状態変更操作(collectなど)は注意 |
| 複雑フィルタ/マップ/リダクション | Stream | for文だとコードが長くなる |
パフォーマンスを優先するなら for文 で、
読みやすさを優先するなら Stream、
大規模並列処理なら parallelStream、
を使うのが良いようです。
for文 は昔からある処理で遅いのかと思っていましたら、意外と処理が速いことに驚きました。
Stream を使うとなんだかカッコいいし、今回のことを念頭に置きながら for文 と Stream を使い分けていきたいと思いました。
最後までお読みいただきありがとうございました。
Java言語を基礎から学びたいと思った方はこちらがおすすめです。
リンク
実践編はこちらがおすすめです。
リンク
























コメントを残す