Потоки 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
Пишіть, який би код був більш зрозумілим у ситуації. Різниця у виконанні мінімальна (і дуже ситуативна).
Брайан Гец

5
Забудьте про нанооптимізацію та використовуйте добре читаний та доступний код. з потоками завжди слід використовувати кожну операцію окремо, включаючи фільтри.
Діабло

Відповіді:


151

Код, який повинен бути виконаний для обох альтернатив, настільки схожий, що не можна надійно передбачити результат. Основна структура об'єкта може відрізнятися, але це не є викликом для оптимізатора гарячої точки. Тож це залежить від інших навколишніх умов, які принесуть швидше виконання, якщо є якась різниця.

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

Але, як було сказано, цей вид накладних витрат буде ліквідований оптимізатором HotSpot і є незначним.

Теоретично два фільтри можна простіше паралелізувати, ніж один фільтр, але це актуально лише для досить обчислювальних інтенсивних завдань¹.

Тож простої відповіді немає.

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


¹… і вимагає впровадження, що виконує паралельну обробку наступних етапів, дорогу, яку зараз не бере стандартна реалізація потоку


4
чи не повинен код повторювати отриманий потік після кожного фільтра?
jucardi

13
@Juan Carlos Diaz: ні, потоки не працюють таким чином. Прочитайте про "ледачу оцінку"; проміжні операції нічого не роблять, вони лише змінюють результат термінальної операції.
Холгер

34

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

Для малого масиву пропускна здатність 10 с / с: Масив 10 елементів для середнього 10 000 елементів пропускна здатність / с: введіть тут опис зображення для великого масиву 1 000 000 елементів пропускна здатність / с: 1М елементів

ПРИМІТКА: тести виконуються

  • 8 ЦП
  • 1 Гб оперативної пам’яті
  • Версія ОС: 16.04.1 LTS (Xenial Xerus)
  • версія java: 1.8.0_121
  • jvm: -XX: + UseG1GC -сервер -Xmx1024m -Xms1024m

ОНОВЛЕННЯ: 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 ДО ТЕСТУ1 тест1, тест1 працює трохи повільніше. Тільки тоді, коли тест запускається першим, він здається швидшим. Чи може хтось відтворити це чи мати якусь інформацію?
Sperr

5
Це може бути пов’язано з тим, що вартість компіляції HotSpot виникає за допомогою будь-якого тесту, який виконується першим.
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}
Параміш Корракуті

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}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.