it-swarm.com.ru

Java 8 потоков: несколько фильтров и сложные условия

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

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

или вы можете сделать то же самое со сложным условием и одиночнымfilter:

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

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

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

187
deamon

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

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

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

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

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

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


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

126
Holger

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

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());
}
19
Hank D

Сложное условие фильтрации лучше с точки зрения производительности, но лучшая производительность покажет старый способ для цикла со стандартным if clause - это лучший вариант. Разница в малом массиве 10 элементов может составлять ~ 2 раза, для большого массива разница не так велика.
Вы можете взглянуть на мой проект GitHub , где я проводил тесты производительности для нескольких вариантов итераций массива.

Для малого массива 10 элементов пропускной способности ops/s: 10 element array Для средней пропускной способности 10000 элементов/с: enter image description here Для большого массива 1 000 000 элементов пропускная способность операций/с: 1M elements

ПРИМЕЧАНИЕ: тесты выполняются на

  • 8 CPU
  • 1 ГБ ОЗУ
  • Версия ОС: 16.04.1 LTS (Xenial Xerus)
  • Версия Java: 1.8.0_121
  • jvm: -XX: + UseG1GC -server -Xmx1024m -Xms1024m
3
Sergii

Это результат 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}
1
Venkat Madhav