multiple - Java 8 Streams: filtros mĂșltiples frente a condiciones complejas
stream java 8 ejemplo (3)
El código que debe ejecutarse para ambas alternativas es tan similar que no puede predecir un resultado de manera confiable. La estructura del objeto subyacente puede diferir, pero eso no es un desafío para el optimizador del punto de acceso. De modo que depende de otras condiciones circundantes que cederán 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 expresiones lambda, por ejemplo, reemplazar filter(x -> x.isCool())
por filter(ItemType::isCool)
. De esa forma, habrá 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étodos podría crear el mismo o menor código de delegación que una invocación de filter
único usando una expresión lambda con &&
.
Pero, como se dijo, este tipo de gastos generales 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 filtro, pero eso solo es relevante para tareas intensas en vez de computación¹.
Entonces no hay una respuesta simple.
En resumidas cuentas, no piense en dichas diferencias de rendimiento por debajo del umbral de detección de olores. Usa lo que es más legible
¹ ... y requeriría una implementación haciendo el procesamiento paralelo de las etapas subsiguientes, un camino actualmente no tomado por la implementación estándar de Stream
Algunas veces quiere 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 un solo filter
:
myList.stream().filter(x -> x.size() > 10 && x -> x.isCool()) ...
Supongo 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?
En un conjunto, tengo 10 mil referencias a objetos distintos. Luego hice la "prueba de velocidad":
long time1 = System.currentTimeMillis();
users.stream()
.filter((u) -> u.getGender() == Gender.FEMALE && u.getAge() % 2 == 0);
long time2 = System.currentTimeMillis();
System.out.printf("%d milli seconds", time2 - time1);
Después de 10 ejecuciones, el tiempo promedio fue de 45,5 milisegundos. A continuación, utilizo multi filter ():
long time1 = System.currentTimeMillis();
users.stream()
.filter((u) -> u.getGender() == Gender.FEMALE)
.filter((u) -> u.getAge() % 2 == 0);
long time2 = System.currentTimeMillis();
System.out.printf("%d milli seconds", time2 - time1);
El tiempo promedio fue de 44,7 milisegundos.
Por supuesto, esta es una prueba con operaciones simples, pero creo que no hay diferencia, porque Stream usa la iteración interna, a diferencia de los bucles "for" que usan iteración externa, en otras palabras, el bucle ocurre solo una vez y una vez completado, el resultado es devuelto. Es probable que haya una diferencia en el tiempo que el compilador tomará para leer los filtros, que es insignificante.
Esta prueba muestra que su segunda opción puede funcionar significativamente mejor. Primero los hallazgos, 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());
}