Java 8 Akışları: karmaşık koşullara karşı çoklu filtreler


235

Bazen bir filtreyi Streambirden fazla koşulla filtrelemek istersiniz :

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

ya da karmaşık bir durum ve tek bir durumla aynı şeyi yapabilirsiniz filter:

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

Tahminimce ikinci yaklaşım daha iyi performans özelliklerine sahip, ama bunu bilmiyorum .

İlk yaklaşım okunabilirliği kazanır, ancak performans için daha iyi olan nedir?


57
Durumda hangi kodu daha okunabilir yazabilirsiniz. Performans farkı minimumdur (ve oldukça durumsaldır).
Brian Goetz

5
Nano optimizasyonları unutun ve yüksek oranda okunabilir ve bakımı kolay kod kullanın. akışlarda, filtreler dahil olmak üzere her işlemi ayrı ayrı kullanmalısınız.
Diablo

Yanıtlar:


151

Her iki alternatif için de yürütülmesi gereken kod o kadar benzer ki bir sonucu güvenilir bir şekilde tahmin edemezsiniz. Temel nesne yapısı farklı olabilir, ancak bu etkin nokta optimize edici için bir zorluk oluşturmaz. Bu nedenle, herhangi bir fark varsa, daha hızlı bir yürütme sağlayacak diğer çevre koşullarına bağlıdır.

İki filtre örneklerini birleştiren daha fazla nesne ve dolayısıyla daha delegating kodu oluşturur ama yerine örneğin lambda ifadeleri yerine yöntemi başvuruları kullanıyorsanız bu değiştirebilir filter(x -> x.isCool())tarafından filter(ItemType::isCool). Böylece lambda ifadeniz için oluşturulan sentetik temsilci seçme yöntemini ortadan kaldırmış olursunuz. Dolayısıyla, iki filtreyi iki yöntem başvurusu kullanarak birleştirmek filter, lambda ifadesi kullanan tek bir çağrıdan aynı veya daha az temsilci kodu oluşturabilir &&.

Ancak, daha önce de belirtildiği gibi, bu tür ek yük HotSpot optimizer tarafından ortadan kaldırılacak ve ihmal edilebilir.

Teorik olarak, iki filtre tek bir filtreden daha kolay paralelleştirilebilir, ancak bu yalnızca oldukça hesaplamalı yoğun görevler için geçerlidir¹.

Yani basit bir cevap yok.

Sonuç olarak, koku algılama eşiğinin altındaki performans farklılıklarını düşünmeyin. Daha okunabilir olanı kullanın.


¹… ve şu anda standart Akış uygulaması tarafından alınmayan bir yol olan sonraki aşamaların paralel işlenmesini gerektiren bir uygulama gerektirir


4
kodun her filtreden sonra elde edilen akışı yinelemesi gerekmez mi?
jucardi

13
@Juan Carlos Diaz: hayır, akışlar bu şekilde çalışmıyor. “Tembel değerlendirme” hakkında bilgi edinin; Ara işlemler hiçbir şey yapmaz, sadece terminal işleminin sonucunu değiştirirler.
Holger

34

Karmaşık bir filtre koşulu performans açısından daha iyidir, ancak en iyi performans standart bir döngü için eski moda gösterecektir if clauseen iyi seçenektir. Küçük bir dizi 10 element farkı üzerindeki fark ~ 2 kez olabilir, büyük bir dizi için fark o kadar büyük değildir. Birden çok dizi yineleme seçeneği için performans testleri
yaptığım GitHub projeme göz atabilirsiniz

Küçük dizi 10 elementli işlem ops / s için: 10 eleman dizisi Orta 10.000 elementli işlem ops / s için: resim açıklamasını buraya girin Büyük dizi için 1.000.000 elementli işlem ops / s: 1M elemanları

NOT: testler devam eder

  • 8 CPU
  • 1 GB RAM
  • İşletim sistemi sürümü: 16.04.1 LTS (Xenial Xerus)
  • java sürümü: 1.8.0_121
  • jvm: -XX: + KullanımG1GC -server -Xmx1024m -Xms1024m

GÜNCELLEME: Java 11'in performansı konusunda bir miktar ilerleme kaydedildi, ancak dinamikler aynı kaldı

Deney modu: Verim, ops / saat Java 8vs11


22

Bu test, ikinci seçeneğinizin çok daha iyi performans gösterebileceğini göstermektedir. Önce bulgular, sonra kod:

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}

şimdi kod:

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
İlginç - test1 ÖNCE test2 çalıştırma sırasını değiştirdiğimde, test1 biraz daha yavaş çalışır. Sadece test1 ilk çalıştığında daha hızlı görünür. Herkes bunu çoğaltabilir veya herhangi bir öngörü olabilir mi?
Sperr

5
Bunun nedeni, HotSpot derlemesinin maliyetinin, ilk önce yapılan herhangi bir testten kaynaklanması olabilir.
DaBlick

@ Haklısınız, sipariş değiştiğinde sonuçlar tahmin edilemez. Ancak, bunu üç farklı iş parçacığıyla çalıştırdığımda, önce hangi iş parçacığının başlayacağına bakılmaksızın her zaman daha iyi sonuçlar veren karmaşık filtre. Sonuçlar aşağıdadır. 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

Bu @Hank D tarafından paylaşılan 6 farklı örnek test kombinasyonunun sonucudur. Form yükleminin u -> exp1 && exp2tüm durumlarda yüksek performans gösterdiği açıktır .

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}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.