157

私は Java 8 を初めて使用します。API の詳細はまだわかりませんが、新しい Streams API と古き良き Collections のパフォーマンスを比較するための小さな非公式のベンチマークを作成しました。

このテストでは、 のリストをフィルタリングし、Integer偶数ごとに平方根を計算して の結果Listに格納しDoubleます。

コードは次のとおりです。

    public static void main(String[] args) {
        //Calculating square root of even numbers from 1 to N       
        int min = 1;
        int max = 1000000;

        List<Integer> sourceList = new ArrayList<>();
        for (int i = min; i < max; i++) {
            sourceList.add(i);
        }

        List<Double> result = new LinkedList<>();


        //Collections approach
        long t0 = System.nanoTime();
        long elapsed = 0;
        for (Integer i : sourceList) {
            if(i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Stream approach
        Stream<Integer> stream = sourceList.stream();       
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


        //Parallel stream approach
        stream = sourceList.stream().parallel();        
        t0 = System.nanoTime();
        result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
        elapsed = System.nanoTime() - t0;       
        System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
    }.

デュアル コア マシンの結果は次のとおりです。

    Collections: Elapsed time:        94338247 ns   (0,094338 seconds)
    Streams: Elapsed time:           201112924 ns   (0,201113 seconds)
    Parallel streams: Elapsed time:  357243629 ns   (0,357244 seconds)

この特定のテストでは、ストリームはコレクションの約 2 倍遅く、並列処理は役に立ちません (または、間違った方法で使用していますか?)。

質問:

  • このテストは公平ですか?私は間違いを犯しましたか?
  • ストリームはコレクションより遅いですか? 誰かがこれについて正式なベンチマークを作成しましたか?
  • どのアプローチを目指すべきですか?

結果を更新しました。

@pveentjer のアドバイスに従って、JVM ウォームアップ (1k イテレーション) の後にテストを 1k 回実行しました。

    Collections: Average time:      206884437,000000 ns     (0,206884 seconds)
    Streams: Average time:           98366725,000000 ns     (0,098367 seconds)
    Parallel streams: Average time: 167703705,000000 ns     (0,167704 seconds)

この場合、ストリームはよりパフォーマンスが高くなります。フィルタリング関数が実行時に 1 回か 2 回しか呼び出されないアプリでは、何が観察されるのだろうか。

4

5 に答える 5

215
  1. LinkedListイテレータを使用してリストの途中から大量に削除する場合以外は、使用をやめてください。

  2. ベンチマーク コードを手動で記述するのはやめて、JMHを使用してください。

適切なベンチマーク:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(StreamVsVanilla.N)
public class StreamVsVanilla {
    public static final int N = 10000;

    static List<Integer> sourceList = new ArrayList<>();
    static {
        for (int i = 0; i < N; i++) {
            sourceList.add(i);
        }
    }

    @Benchmark
    public List<Double> vanilla() {
        List<Double> result = new ArrayList<>(sourceList.size() / 2 + 1);
        for (Integer i : sourceList) {
            if (i % 2 == 0){
                result.add(Math.sqrt(i));
            }
        }
        return result;
    }

    @Benchmark
    public List<Double> stream() {
        return sourceList.stream()
                .filter(i -> i % 2 == 0)
                .map(Math::sqrt)
                .collect(Collectors.toCollection(
                    () -> new ArrayList<>(sourceList.size() / 2 + 1)));
    }
}

結果:

Benchmark                   Mode   Samples         Mean   Mean error    Units
StreamVsVanilla.stream      avgt        10       17.588        0.230    ns/op
StreamVsVanilla.vanilla     avgt        10       10.796        0.063    ns/op

予想通り、ストリームの実装はかなり遅いです。JIT はすべてのラムダ要素をインライン化できますが、バニラ バージョンほど完全に簡潔なコードを生成しません。

一般に、Java 8 ストリームは魔法ではありません。Iterable.forEach()すでに十分に実装されているもの (おそらく、単純な反復または Java 5 の for-each ステートメントをandCollection.removeIf()呼び出しに置き換えたもの) を高速化することはできませんでした。ストリームは、コーディングの利便性と安全性に重点を置いています。利便性 -- ここでは速度のトレードオフが機能しています。

于 2014-03-26T18:48:06.123 に答える
18

1) ベンチマークを使用すると、1 秒未満の時間が表示されます。つまり、副作用が結果に大きな影響を与える可能性があるということです。だから、私はあなたの仕事を10倍に増やしました

    int max = 10_000_000;

ベンチマークを実行しました。私の結果:

Collections: Elapsed time:   8592999350 ns  (8.592999 seconds)
Streams: Elapsed time:       2068208058 ns  (2.068208 seconds)
Parallel streams: Elapsed time:  7186967071 ns  (7.186967 seconds)

編集なし ( int max = 1_000_000) の結果は

Collections: Elapsed time:   113373057 ns   (0.113373 seconds)
Streams: Elapsed time:       135570440 ns   (0.135570 seconds)
Parallel streams: Elapsed time:  104091980 ns   (0.104092 seconds)

それはあなたの結果のようなものです: ストリームはコレクションよりも遅いです。結論:ストリームの初期化/値の送信に多くの時間が費やされました。

2) タスク ストリームを増やした後は高速になりましたが (それで問題ありません)、並列ストリームは依然として遅すぎます。どうしたの?注:あなたはcollect(Collectors.toList())あなたのコマンドを持っています。単一のコレクションに収集すると、同時実行の場合にパフォーマンスのボトルネックとオーバーヘッドが発生します。を置き換えることで、間接費の相対的なコストを見積もることができます。

collecting to collection -> counting the element count

ストリームの場合は、 で実行できますcollect(Collectors.counting())。私は結果を得ました:

Collections: Elapsed time:   41856183 ns    (0.041856 seconds)
Streams: Elapsed time:       546590322 ns   (0.546590 seconds)
Parallel streams: Elapsed time:  1540051478 ns  (1.540051 seconds)

それは大きな仕事です!( int max = 10000000)結論:アイテムをコレクションに集めるのに大部分の時間がかかりました。最も遅い部分はリストへの追加です。ところで、 simpleArrayListはに使用されCollectors.toList()ます。

于 2014-03-26T11:43:45.670 に答える
5
    public static void main(String[] args) {
    //Calculating square root of even numbers from 1 to N       
    int min = 1;
    int max = 10000000;

    List<Integer> sourceList = new ArrayList<>();
    for (int i = min; i < max; i++) {
        sourceList.add(i);
    }

    List<Double> result = new LinkedList<>();


    //Collections approach
    long t0 = System.nanoTime();
    long elapsed = 0;
    for (Integer i : sourceList) {
        if(i % 2 == 0){
            result.add( doSomeCalculate(i));
        }
    }
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Stream approach
    Stream<Integer> stream = sourceList.stream();       
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i -> doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));


    //Parallel stream approach
    stream = sourceList.stream().parallel();        
    t0 = System.nanoTime();
    result = stream.filter(i -> i%2 == 0).map(i ->  doSomeCalculate(i))
            .collect(Collectors.toList());
    elapsed = System.nanoTime() - t0;       
    System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));      
}

static double doSomeCalculate(int input) {
    for(int i=0; i<100000; i++){
        Math.sqrt(i+input);
    }
    return Math.sqrt(input);
}

コードを少し変更し、8 コアの mac book pro で実行したところ、妥当な結果が得られました。

Collections: Elapsed time:      1522036826 ns   (1.522037 seconds)
Streams: Elapsed time:          4315833719 ns   (4.315834 seconds)
Parallel streams: Elapsed time:  261152901 ns   (0.261153 seconds)
于 2015-11-19T19:31:12.927 に答える