Java 8 de Corrientes: varios filtros vs condición compleja


A veces desea filtrar un Stream con más de una condición:

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

O podría hacer lo mismo con una condición compleja y una única filter:

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

Mi conjetura es que el segundo enfoque tiene mejores características de rendimiento, pero no lo sé .

El primer enfoque gana en legibilidad, pero ¿qué es mejor para el rendimiento?

Author: Holger, 2014-06-05

2 answers

El código que debe ejecutarse para ambas alternativas es tan similar que no se puede predecir un resultado de manera confiable. La estructura de objetos subyacente puede diferir, pero eso no es un desafío para el optimizador de hotspot. Por lo tanto, depende de otras condiciones circundantes que darán lugar a una ejecución más rápida, si hay alguna diferencia.

La combinación de dos instancias de filtro crea más objetos y, por lo tanto, más código de delegación, pero esto puede cambiar si utiliza referencias de métodos en lugar de lambda expresiones, por ejemplo, reemplazar filter(x -> x.isCool()) por filter(ItemType::isCool). De esta manera ha eliminado el método de delegación sintético creado para su expresión lambda. Así que combinar dos filtros usando dos referencias de método podría crear el mismo o menor código de delegación que una única invocación filter usando una expresión lambda con &&.

Pero, como se ha dicho, este tipo de sobrecarga será eliminado por el optimizador de HotSpot y es insignificante.

En teoría, dos filtros podrían ser más fáciles de paralelizar que un solo filtrar, pero eso solo es relevante para tareas intensas1.

Así que no hay una respuesta simple.

La conclusión es, no piense en tales diferencias de rendimiento por debajo del umbral de detección de olores. Usa lo que sea más legible.


1 require y requeriría una implementación haciendo procesamiento paralelo de etapas posteriores, un camino actualmente no tomado por la implementación de flujo estándar

 97
Author: Holger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-22 14:48:46

Esta prueba muestra que su segunda opción puede funcionar significativamente mejor. Hallazgos primero, luego el código:

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}

Ahora el código:

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
Author: Hank D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-09 20:05:59