web-dev-qa-db-ja.com

Java 8 Streams:複数のフィルタと複雑な条件

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

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

あるいは、複雑な条件と単一のfilterでも同じことができます。

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

私の推測では、2番目のアプローチはより良いパフォーマンス特性を持っていますが、私は知りません。

最初のアプローチは読みやすさに勝ちますが、パフォーマンスにとって何が良いのでしょうか。

187
deamon

どちらの方法でも実行する必要があるコードは非常に似ているため、確実に結果を予測することはできません。基本的なオブジェクト構造は異なる場合がありますが、ホットスポットオプティマイザーにとってそれは難題ではありません。そのため、何らかの違いがある場合は、他の周囲の条件に依存して実行速度が速くなります。

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

しかし、前述のように、この種のオーバーヘッドはHotSpotオプティマイザによって排除され、ごくわずかです。

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

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

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


¹…そして後続のステージの並列処理を実行する実装を必要とするでしょう、現在標準のStream実装によって取られていない道

126
Holger

このテストはあなたの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());
}
19
Hank D

複雑なフィルタ条件はパフォーマンスの観点からは優れていますが、標準のif clauseを使用したループでは、最高のパフォーマンスが昔ながらの方法であることが最善の選択肢です。小さい配列での10要素の違いは2倍になるかもしれませんが、大きい配列では違いはそれほど大きくありません。
複数の配列反復オプションのパフォーマンステストを行った私の GitHubプロジェクト を見てください。

スモールアレイ10要素スループットops/sの場合: 10 element array 中規模の10,000要素のスループットops/sの場合: enter image description here 大規模配列の1,000,000要素のスループットops/sの場合: 1M elements

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

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

これは@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}
1
Venkat Madhav