Java 8ストリーム:複数のフィルターと複雑な条件


235

Stream複数の条件でaをフィルタリングしたい場合があります。

myList.stream().filter(x -> x.size() > 10).filter(x -> x.isCool()) ...

または、複雑な条件と単一の 条件で同じことを行うことができますfilter

myList.stream().filter(x -> x.size() > 10 && x -> x.isCool()) ...

2つ目のアプローチの方がパフォーマンス特性は優れていると思いますが、それはわかりません。

最初のアプローチは読みやすさで勝ちますが、パフォーマンスのために何が良いですか?


57
その状況で読みやすいコードを記述します。パフォーマンスの違いはごくわずかです(状況によっては異なります)。
Brian Goetz 2014年

5
ナノ最適化を忘れて、読みやすく保守しやすいコードを使用します。ストリームでは、フィルターを含めて各オペレーションを常に別々に使用する必要があります。
ディアブロ

回答:


151

両方の方法で実行する必要があるコードは非常に似ているため、結果を確実に予測することはできません。基本となるオブジェクトの構造は異なる場合がありますが、ホットスポットオプティマイザーへの挑戦はありません。したがって、違いがある場合、より高速に実行される他の周囲の条件に依存します。

2つのフィルタのインスタンスを組み合わせることで、より多くのオブジェクトとなり、より委任コードを作成しますが、あなたがメソッド参照ではなく、ラムダ式を使用している場合、これは変更することができ、例えば置き換えるfilter(x -> x.isCool())ことでfilter(ItemType::isCool)。このようにして、ラムダ式用に作成された合成委任メソッドを削除しました。したがって、2つのメソッド参照を使用して2つのフィルターを組み合わせるとfilter、ラムダ式を使用した1回の呼び出しと同じか、それより少ない委任コードが作成される&&

ただし、前述のように、この種のオーバーヘッドはHotSpotオプティマイザーによって排除され、無視できます。

理論的には、2つのフィルターは単一のフィルターよりも簡単に並列化できますが、それはかなり計算量の多いタスクにのみ関連します¹。

簡単な答えはありません。

肝心な点は、臭気検出しきい値を下回るパフォーマンスの違いについては考えないことです。より読みやすいものを使用してください。


¹…そして、後続のステージの並列処理を行う実装が必要になります。現在、標準のStream実装では採用されていない道です。


4
コードは、各フィルターの後に結果のストリームを反復する必要はありませんか?
ジュカルディ2016年

13
@ファンカルロスディアス:いいえ、ストリームはそのようには機能しません。「遅延評価」について読んでください。中間操作は何もせず、端末操作の結果を変更するだけです。
Holger

34

複雑なフィルター条件は、パフォーマンスの観点ではより優れていますが、最高のパフォーマンスは、標準のループの古い方法を示しif clauseますが、最良のオプションです。小さな配列10要素の違いの違いは2倍になる可能性があります。大きな配列の場合、違いはそれほど大きくありません。複数の配列反復オプションのパフォーマンステストを行っ
た私のGitHubプロジェクトをご覧ください。

小規模なアレイの場合、10要素のスループットオペレーション/秒: 10要素配列 中規模の10,000要素のスループットオペレーション/秒の場合: ここに画像の説明を入力してください 大規模なアレイの場合、1,000,000要素のスループットオペレーション/秒: 100万要素

注:テストは次で実行されます

  • 8 CPU
  • 1 GBのRAM
  • OSバージョン:16.04.1 LTS(Xenial Xerus)
  • Javaバージョン:1.8.0_121
  • jvm:-XX:+ UseG1GC -server -Xmx1024m -Xms1024m

更新: Java 11はパフォーマンスにある程度の進歩がありますが、ダイナミクスは同じままです

ベンチマークモード:スループット、操作/時間 Java 8vs11


22

このテストは、2番目のオプションのパフォーマンスが大幅に向上することを示しています。最初に結果、次にコード:

one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=4142, min=29, average=41.420000, max=82}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=13315, min=117, average=133.150000, max=153}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10320, min=82, average=103.200000, max=127}

今コード:

enum Gender {
    FEMALE,
    MALE
}

static class User {
    Gender gender;
    int age;

    public User(Gender gender, int age){
        this.gender = gender;
        this.age = age;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

static long test1(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter((u) -> u.getGender() == Gender.FEMALE && u.getAge() % 2 == 0)
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

static long test2(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter(u -> u.getGender() == Gender.FEMALE)
            .filter(u -> u.getAge() % 2 == 0)
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

static long test3(List<User> users){
    long time1 = System.currentTimeMillis();
    users.stream()
            .filter(((Predicate<User>) u -> u.getGender() == Gender.FEMALE).and(u -> u.getAge() % 2 == 0))
            .allMatch(u -> true);                   // least overhead terminal function I can think of
    long time2 = System.currentTimeMillis();
    return time2 - time1;
}

public static void main(String... args) {
    int size = 10000000;
    List<User> users =
    IntStream.range(0,size)
            .mapToObj(i -> i % 2 == 0 ? new User(Gender.MALE, i % 100) : new User(Gender.FEMALE, i % 100))
            .collect(Collectors.toCollection(()->new ArrayList<>(size)));
    repeat("one filter with predicate of form u -> exp1 && exp2", users, Temp::test1, 100);
    repeat("two filters with predicates of form u -> exp1", users, Temp::test2, 100);
    repeat("one filter with predicate of form predOne.and(pred2)", users, Temp::test3, 100);
}

private static void repeat(String name, List<User> users, ToLongFunction<List<User>> test, int iterations) {
    System.out.println(name + ", list size " + users.size() + ", averaged over " + iterations + " runs: " + IntStream.range(0, iterations)
            .mapToLong(i -> test.applyAsLong(users))
            .summaryStatistics());
}

3
興味深い-test1の前にtest2を実行するように順序を変更すると、test1の実行が少し遅くなります。test1が最初に実行されたときだけ、高速に見えます。誰かがこれを再現したり、洞察を得たりできますか?
Sperr 2017

5
これは、HotSpotコンパイルのコストが、最初に実行されるテストによって発生するためと考えられます。
DaBlick

@Sperr正解です。順序が変更された場合、結果は予測できません。しかし、これを3つの異なるスレッドで実行すると、どちらのスレッドが最初に開始されるかに関係なく、常に複雑なフィルターがより良い結果をもたらします。結果は以下のとおりです。Test #1: {count=100, sum=7207, min=65, average=72.070000, max=91} Test #3: {count=100, sum=7959, min=72, average=79.590000, max=97} Test #2: {count=100, sum=8869, min=79, average=88.690000, max=110}
Paramesh Korrakuti

2

これは、@ Hank Dが共有するサンプルテストの6つの異なる組み合わせの結果ですu -> exp1 && exp2。フォームの述語がすべてのケースで高いパフォーマンスを発揮することは明らかです。

one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=3372, min=31, average=33.720000, max=47}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9150, min=85, average=91.500000, max=118}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9046, min=81, average=90.460000, max=150}

one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8336, min=77, average=83.360000, max=189}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9094, min=84, average=90.940000, max=176}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10501, min=99, average=105.010000, max=136}

two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=11117, min=98, average=111.170000, max=238}
one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8346, min=77, average=83.460000, max=113}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9089, min=81, average=90.890000, max=137}

two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10434, min=98, average=104.340000, max=132}
one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9113, min=81, average=91.130000, max=179}
one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8258, min=77, average=82.580000, max=100}

one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=9131, min=81, average=91.310000, max=139}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10265, min=97, average=102.650000, max=131}
one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8442, min=77, average=84.420000, max=156}

one filter with predicate of form predOne.and(pred2), list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8553, min=81, average=85.530000, max=125}
one filter with predicate of form u -> exp1 && exp2, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=8219, min=77, average=82.190000, max=142}
two filters with predicates of form u -> exp1, list size 10000000, averaged over 100 runs: LongSummaryStatistics{count=100, sum=10305, min=97, average=103.050000, max=132}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.