Вопрос по java, java-8, java-stream, filter, lambda – Java 8 Streams: несколько фильтров против сложных условий

170

Иногда вы хотите отфильтроватьStream с более чем одним условием:

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

или вы могли бы сделать то же самое со сложным состоянием ине замужем filter:

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

Я предполагаю, что второй подход имеет лучшие характеристики производительности, но я незнать Это.

Первый подход выигрывает в удобочитаемости, но что лучше для производительности?

Забудьте о нано-оптимизации и используйте хорошо читаемый и поддерживаемый код. с потоками всегда следует использовать каждую операцию отдельно, включая фильтры. Diablo
Напишите любой код, который будет более читабельным в данной ситуации. Разница в производительности минимальна (и очень ситуативна). Brian Goetz

Ваш Ответ

3   ответа
1

совместно используемых @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}
118

который должен быть выполнен для обеих альтернатив, настолько схож, что вы не можете надежно предсказать результат. Базовая структура объекта может отличаться, но это не является проблемой для оптимизатора точки доступа. Так что это зависит от других окружающих условий, которые приведут к более быстрому выполнению, если есть какая-либо разница.

Объединение двух экземпляров фильтра создает больше объектов и, следовательно, больше делегирующего кода, но это может измениться, если вы будете использовать ссылки на методы, а не на лямбда-выражения, например замещатьfilter(x -> x.isCool()) отfilter(ItemType::isCool), Таким образом, вы исключили синтетический метод делегирования, созданный для вашего лямбда-выражения. Таким образом, объединение двух фильтров с использованием двух ссылок на методы может создать такой же или меньший код делегирования, чем одинfilter вызов с использованием лямбда-выражения с&&.

Но, как уже говорилось, этот вид издержек будет устранен оптимизатором HotSpot и незначителен.

Теоретически, два фильтра могут быть легче распараллелены, чем один фильтр, но это актуально только для довольно сложных вычислительных задач ».

Так что нет простого ответа.

Суть в том, что не думайте о таких различиях в производительности ниже порога обнаружения запаха. Используйте то, что более читабельно.

And… и потребуется реализация, выполняющая параллельную обработку последующих этапов, путь, который в настоящее время не принят стандартной реализацией Stream

@Juan Карлос Диас: нет, потоки не работают таким образом. Читайте о «ленивой оценке»; промежуточные операции ничего не делают, они только изменяют результат работы терминала. Holger
не код должен повторять полученный поток после каждого фильтра? jucardi
20

что ваш второй вариант может работать значительно лучше. Сначала результаты, а затем код:

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());
}
Интересно - когда я меняю порядок запуска test2 ДО test1, test1 работает немного медленнее. Только когда тест1 запускается первым, он кажется быстрее. Кто-нибудь может воспроизвести это или есть какие-то идеи? Sperr
Возможно, это связано с тем, что стоимость компиляции HotSpot зависит от того, какой тест запускается первым. DaBlick

Похожие вопросы