【Java】for文とStream、実際のところどちらが速いの?

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など)は注意
複雑フィルタ/マップ/リダクションStreamfor文だとコードが長くなる

パフォーマンスを優先するなら for文 で、
読みやすさを優先するなら Stream、
大規模並列処理なら parallelStream、
を使うのが良いようです。

for文 は昔からある処理で遅いのかと思っていましたら、意外と処理が速いことに驚きました。

Stream を使うとなんだかカッコいいし、今回のことを念頭に置きながら for文 と Stream を使い分けていきたいと思いました。

最後までお読みいただきありがとうございました。


Java入門新定番を謳う技術書。

広告

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


ABOUT US
ぽむ
はじめまして! ぽむ と申します。 Java、Kotlin (Android)、VBAなどの開発経験があるITエンジニアです。 備忘録として始めたブログですがみなさまのお役に立てたら光栄です。 英語など IT 以外の話題にも触れていこうと思っています。 詳しい自己紹介についてはこちら! よろしくお願いします。