जावा 8 स्ट्रीम: कई फिल्टर बनाम जटिल स्थिति


235

कभी-कभी आप एक Streamसे अधिक शर्तों के साथ फ़िल्टर करना चाहते हैं:

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

या आप एक जटिल स्थिति और एकल के साथ भी ऐसा कर सकते हैं filter:

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

मेरा अनुमान है कि दूसरे दृष्टिकोण में बेहतर प्रदर्शन विशेषताएं हैं, लेकिन मुझे यह पता नहीं है।

पहला दृष्टिकोण पठनीयता में जीतता है, लेकिन प्रदर्शन के लिए बेहतर क्या है?


57
जो भी कोड लिखें स्थिति में अधिक पठनीय है। प्रदर्शन अंतर न्यूनतम (और अत्यधिक स्थितिजन्य) है।
ब्रायन गोएटज

5
नैनो-अनुकूलन के बारे में भूल जाओ और अत्यधिक पठनीय और बनाए रखने योग्य कोड का उपयोग करें। धाराओं के साथ, एक को हमेशा फिल्टर सहित प्रत्येक ऑपरेशन का उपयोग करना चाहिए।
डियाब्लो

जवाबों:


151

दोनों विकल्पों के लिए जिस कोड को निष्पादित किया जाना है, वह इतना समान है कि आप किसी परिणाम का विश्वसनीय अनुमान नहीं लगा सकते। अंतर्निहित ऑब्जेक्ट संरचना भिन्न हो सकती है, लेकिन हॉटस्पॉट ऑप्टिमाइज़र के लिए यह कोई चुनौती नहीं है। इसलिए यह अन्य आस-पास की स्थितियों पर निर्भर करता है जो किसी भी तरह के अंतर होने पर तेजी से निष्पादन के लिए तैयार होंगे।

दो फिल्टर उदाहरणों का मेल अधिक वस्तुओं और इसलिए अधिक सौंपने कोड बनाता है, लेकिन यदि आप, लैम्ब्डा भाव के बजाय विधि संदर्भ का उपयोग जैसे की जगह इसे बदल सकते हैं filter(x -> x.isCool())द्वारा filter(ItemType::isCool)। इस तरह आपने अपने लैम्ब्डा अभिव्यक्ति के लिए बनाई गई सिंथेटिक प्रतिनिधि विधि को समाप्त कर दिया है। इसलिए दो विधि संदर्भों का उपयोग करते हुए दो फिल्टर को मिलाकर एक एकल filterमंगलाचरण की तुलना में समान या कम प्रतिनिधि कोड बना सकते हैं &&

लेकिन, जैसा कि कहा गया है, इस तरह के ओवरहेड को हॉटस्पॉट ऑप्टिमाइज़र द्वारा समाप्त कर दिया जाएगा और नगण्य है।

सिद्धांत रूप में, दो फ़िल्टर एकल फ़िल्टर की तुलना में आसान समानांतर किए जा सकते हैं लेकिन यह केवल कम्प्यूटेशनल गहन कार्यों के लिए प्रासंगिक है।

तो कोई सरल उत्तर नहीं है।

लब्बोलुआब यह है कि गंध का पता लगाने की सीमा के नीचे इस तरह के प्रदर्शन अंतर के बारे में मत सोचो। अधिक पठनीय का उपयोग करें।


¹… और बाद के चरणों के समानांतर प्रसंस्करण करने वाले कार्यान्वयन की आवश्यकता होगी, वर्तमान में मानक स्ट्रीम कार्यान्वयन द्वारा एक सड़क नहीं ली गई है


4
प्रत्येक फ़िल्टर के बाद परिणामी धारा को पुनरावृत्त करने के लिए कोड नहीं है?
जुकरडी

13
@ जुआन कार्लोस डियाज़: नहीं, धाराएँ उस तरह से काम नहीं करती हैं। "आलसी मूल्यांकन" के बारे में पढ़ें; मध्यवर्ती संचालन कुछ भी नहीं करते हैं, वे केवल टर्मिनल ऑपरेशन के परिणाम को बदलते हैं।
होल्गर

34

प्रदर्शन के परिप्रेक्ष्य में एक जटिल फ़िल्टर स्थिति बेहतर है, लेकिन सबसे अच्छा प्रदर्शन एक मानक के साथ पाश के लिए पुराने फैशन को दिखाएगा if clauseसबसे अच्छा विकल्प है। एक छोटे सरणी पर 10 तत्वों का अंतर ~ 2 गुना हो सकता है, बड़े सरणी के लिए यह अंतर उतना बड़ा नहीं है।
आप मेरी GitHub परियोजना पर एक नज़र डाल सकते हैं , जहाँ मैंने कई सरणी पुनरावृत्ति विकल्पों के लिए प्रदर्शन परीक्षण किए

छोटे सरणी 10 तत्व थ्रूपुट ऑप्स के 10 तत्व सरणी लिए : मध्यम 10,000 तत्वों थ्रूपुट ऑप्स के यहां छवि विवरण दर्ज करें लिए : बड़े सरणी के लिए 1,000,000 तत्व थ्रूपुट ऑप्स / एस: 1M तत्व

नोट: परीक्षण पर चलता है

  • 8 सीपीयू
  • 1 जीबी रैम
  • ओएस संस्करण: 16.04.1 एलटीएस (ज़ेनियल ज़ेरुस)
  • जावा संस्करण: 1.8.0_121
  • jvm: -XX: + UseG1GC -server -Xmx1024m -Xms1024m

अद्यतन: जावा 11 के प्रदर्शन पर कुछ प्रगति हुई है, लेकिन गतिशीलता एक ही है

बेंचमार्क मोड: थ्रूपुट, ऑप्स / टाइम जावा 8 वी 11


22

यह परीक्षण बताता है कि आपका दूसरा विकल्प बेहतर प्रदर्शन कर सकता है। पहले पता, फिर कोड:

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
दिलचस्प है - जब मैं test2 BEFORE test1 चलाने का आदेश बदलता हूं, तो test1 थोड़ा धीमा चलता है। यह केवल तब होता है जब टेस्ट 1 पहले चलता है कि यह तेज लगता है। किसी को भी इस पुन: पेश कर सकते हैं या कोई अंतर्दृष्टि है?
स्पर

5
यह हो सकता है क्योंकि हॉटस्पॉट संकलन की लागत पहले जो भी परीक्षण चलाया जाता है, उसके कारण होता है।
DaBlick

@ आप सही हैं, जब आदेश बदल गया, परिणाम अनुमानित नहीं हैं। लेकिन, जब मैं इसे तीन अलग-अलग थ्रेड्स के साथ चलाता हूं, तो हमेशा जटिल फ़िल्टर बेहतर परिणाम देता है, चाहे जो भी पहले थ्रेड शुरू हो। नीचे परिणाम हैं। 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}
परमेश कोरकुटी

2

यह @ हांक डी द्वारा साझा किए गए नमूना परीक्षण के 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}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.