Java 8 Streams: ตัวกรองหลายตัวเทียบกับเงื่อนไขที่ซับซ้อน


235

บางครั้งคุณต้องการกรอง a ที่Streamมีมากกว่าหนึ่งเงื่อนไข:

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

หรือคุณสามารถทำเช่นเดียวกันกับเงื่อนไขที่ซับซ้อนและเดี่ยว filter :

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

ฉันเดาว่าวิธีการที่สองมีลักษณะการทำงานที่ดีขึ้น แต่ผมไม่ทราบว่ามัน

วิธีแรกชนะได้ในการอ่าน แต่สิ่งที่ดีกว่าสำหรับประสิทธิภาพ?


57
เขียนว่ารหัสใดสามารถอ่านได้มากขึ้นในสถานการณ์ ความแตกต่างด้านประสิทธิภาพมีน้อยที่สุด (และสถานการณ์สูง)
Brian Goetz

5
ลืมเรื่องการเพิ่มประสิทธิภาพของนาโนและใช้รหัสที่สามารถอ่านได้และบำรุงรักษาได้สูง ด้วยสตรีมเราควรใช้การดำเนินการแต่ละอย่างแยกต่างหากรวมถึงตัวกรองเสมอ
Diablo

คำตอบ:


151

รหัสที่ต้องดำเนินการสำหรับทั้งสองตัวเลือกนั้นคล้ายกันมากจนคุณไม่สามารถคาดการณ์ผลลัพธ์ได้อย่างน่าเชื่อถือ โครงสร้างวัตถุพื้นฐานอาจแตกต่างกันไป แต่นั่นก็ไม่ใช่สิ่งท้าทายสำหรับเครื่องมือเพิ่มประสิทธิภาพฮอตสปอต ดังนั้นมันขึ้นอยู่กับสภาพแวดล้อมอื่น ๆ ซึ่งจะให้ผลในการดำเนินการที่รวดเร็วขึ้นหากมีความแตกต่างใด ๆ

รวมสองกรณีกรองสร้างวัตถุมากขึ้นและมากขึ้นด้วยเหตุนี้การมอบหมายรหัส แต่ตอนนี้สามารถเปลี่ยนถ้าคุณใช้การอ้างอิงวิธีการมากกว่าการแสดงออกแลมบ์ดาเช่นแทนที่โดยfilter(x -> x.isCool()) filter(ItemType::isCool)ด้วยวิธีนี้คุณได้กำจัดวิธีการมอบหมายสังเคราะห์ที่สร้างขึ้นสำหรับการแสดงออกแลมบ์ดาของคุณ ดังนั้นการรวมสองตัวกรองที่ใช้อ้างอิงทั้งสองวิธีอาจสร้างรหัสคณะผู้แทนเดียวกันหรือน้อยกว่าเดียวภาวนาโดยใช้การแสดงออกแลมบ์ดาด้วยfilter&&

แต่ดังที่ได้กล่าวมาโอเวอร์เฮดแบบนี้จะถูกลบออกโดยโปรแกรมเพิ่มประสิทธิภาพ HotSpot และไม่สำคัญ

ในทางทฤษฎีแล้วตัวกรองสองตัวนั้นสามารถทำให้ขนานได้ง่ายกว่าตัวกรองเดียว แต่มันมีความเกี่ยวข้องกับงานที่ต้องใช้การคำนวณค่อนข้างมากเท่านั้น

ดังนั้นจึงไม่มีคำตอบง่ายๆ

บรรทัดล่างคืออย่าคิดว่าความแตกต่างด้านประสิทธิภาพนั้นต่ำกว่าขีด จำกัด การตรวจจับกลิ่น ใช้สิ่งที่อ่านได้มากขึ้น


¹…และจะต้องมีการนำไปใช้ในการประมวลผลแบบขนานของขั้นตอนต่อไปซึ่งในปัจจุบันถนนที่ไม่ได้ดำเนินการโดยการนำมาตรฐานไปใช้กับสตรีม


4
รหัสไม่ต้องย้ำกระแสผลลัพธ์หลังจากตัวกรองแต่ละตัว
jucardi

13
@ Juan Carlos Diaz: ไม่กระแสไม่ได้เป็นอย่างนั้น อ่านเกี่ยวกับ“ การประเมินแบบสันหลังยาว”; การดำเนินการระดับกลางไม่ได้ทำอะไรพวกเขาเพียง แต่ปรับเปลี่ยนผลลัพธ์ของการดำเนินการของเทอร์มินัล
Holger

34

เงื่อนไขตัวกรองที่ซับซ้อนจะดีกว่าในมุมมองประสิทธิภาพ แต่ประสิทธิภาพที่ดีที่สุดจะแสดงแฟชั่นแบบเก่าสำหรับลูปที่มีมาตรฐานif clauseเป็นตัวเลือกที่ดีที่สุด ความแตกต่างของความแตกต่างขององค์ประกอบขนาดเล็กจำนวน 10 อาจ ~ 2 ครั้งสำหรับอาร์เรย์ขนาดใหญ่ความแตกต่างนั้นไม่ใหญ่
คุณสามารถดูโครงการ GitHubของฉันซึ่งฉันทำการทดสอบประสิทธิภาพสำหรับตัวเลือกการวนซ้ำหลายแถว

สำหรับการรับส่งข้อมูลอาร์เรย์ขนาดเล็ก 10 รายการ ops / s: 10 องค์ประกอบอาร์เรย์ สำหรับ 10,000 รายการการรับส่งข้อมูลขนาดกลาง ops / s: ป้อนคำอธิบายรูปภาพที่นี่ สำหรับการส่งผ่านองค์ประกอบอาร์เรย์ 1,000,000 รายการขนาดใหญ่ ops / s: องค์ประกอบ 1M

หมายเหตุ: การทดสอบทำงาน

  • 8 CPU
  • RAM 1 GB
  • เวอร์ชั่นของระบบปฏิบัติการ: 16.04.1 LTS (Xenial Xerus)
  • รุ่น java: 1.8.0_121
  • jvm: -XX: + UseG1GC - เซิร์ฟเวอร์ -Xmx1024m -Xms1024m

UPDATE: Java 11 มีความคืบหน้าเกี่ยวกับประสิทธิภาพ แต่การเปลี่ยนแปลงยังคงเหมือนเดิม

โหมดมาตรฐาน: ปริมาณงาน, เวลา / เวลา Java 8vs11


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 ก่อน test1, test1 จะทำงานช้าลงเล็กน้อย เฉพาะเมื่อ test1 รันก่อนที่จะดูเหมือนเร็วกว่า ใครสามารถทำซ้ำนี้หรือมีข้อมูลเชิงลึกใด ๆ
Sperr

5
อาจเป็นเพราะค่าใช้จ่ายในการรวบรวมฮอตสปอตเกิดขึ้นจากการทดสอบใด ๆ ก็ตามที่ดำเนินการก่อน
DaBlick

@Sperr คุณถูกต้องเมื่อคำสั่งซื้อเปลี่ยนไปผลลัพธ์จะไม่สามารถคาดเดาได้ แต่เมื่อฉันรันด้วยเธรดที่แตกต่างกันสามตัวตัวกรองที่ซับซ้อนจะให้ผลลัพธ์ที่ดีกว่าเสมอโดยไม่คำนึงถึงว่าเธรดเริ่มต้นที่ใด ด้านล่างนี้คือผลลัพธ์ 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

นี่เป็นผลจากการรวมตัวอย่างการทดสอบ 6 ชุดที่ @Hank D ใช้ร่วมกันซึ่งเห็นได้ชัดว่าภาคแสดงของรูปแบบ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.