Java 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
상황에서 더 읽기 쉬운 코드를 작성하십시오. 성능 차이는 최소화됩니다 (상황이 매우 높음).
Brian Goetz

5
나노 최적화를 잊고 읽기 쉽고 유지 관리가 쉬운 코드를 사용하십시오. 스트림의 경우 항상 필터를 포함하여 각 작업을 별도로 사용해야합니다.
디아블로

답변:


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 요소

참고 : 테스트는 다음에서 실행됩니다.

  • CPU 8 개
  • 1GB RAM
  • 운영체제 버전 : 16.04.1 LTS (Xenial Xerus)
  • 자바 버전 : 1.8.0_121
  • jvm : -XX : + UseG1GC-서버 -Xmx1024m -Xms1024m

업데이트 : Java 11의 성능이 약간 향상되었지만 역학은 동일하게 유지됩니다.

벤치 마크 모드 : 처리량, ops / 시간 자바 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
흥미있는-test1 전에 test2를 실행하도록 순서를 변경하면 test1이 약간 느리게 실행됩니다. test1이 처음 실행될 때만 더 빨리 보입니다. 누구나 이것을 재현하거나 통찰력을 가질 수 있습니까?
Sperr

5
HotSpot 컴파일 비용은 먼저 실행되는 테스트에 따라 발생하기 때문일 수 있습니다.
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}
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.