2010-01-31 6 views
7

Estaba tratando de encontrar la forma más rápida de contar la cantidad de elementos en una lista que coincida con un filtro específico. En este caso, encontrar cuántos números impares hay en una lista.¿Por qué este genexp funciona peor que una lista de comprensión?

Mientras hace esto, yo estaba sorprendido por los resultados de la comparación de una lista por comprensión frente a la expresión equivalente del generador:

python -m timeit -s "L = xrange(1000000)" "sum([1 for i in L if i & 1])" 
10 loops, best of 3: 109 msec per loop 

python -m timeit -s "L = xrange(1000000)" "sum(1 for i in L if i & 1)" 
10 loops, best of 3: 125 msec per loop 

También he intentado siendo L una lista regular, y diferentes tamaños, pero en todos casos gana la lista de comprensión.

¿Qué está haciendo el genexp que hace que sea más lento en comparación con el listcomp que crea una nueva lista con 1 millón de elementos ...?

(Por cierto, la manera más rápida que encontré fue: x = 1; len(filter(x.__and__, L)) Y el código sí, ya sé escribir como que mata gatitos, estoy haciendo por el gusto de hacerlo.)

Respuesta

15

Cuando la memoria esencialmente ilimitada está disponible (que invariablemente será el caso en pequeños puntos de referencia, aunque a menudo no en problemas del mundo real! -), las listas tenderán a superar a los generadores porque pueden asignarse solo una vez, en un "gran grupo" (sin fragmentación de memoria, etc.) mientras que los generadores requieren (internamente) un esfuerzo adicional para evitar ese enfoque de "gran grupo" al preservar el estado del marco de pila para permitir la reanudación de la ejecución.

Si un enfoque de lista o de generador será más rápido en un programa real depende de la situación de memoria exacta, incluida la fragmentación, que es casi imposible de reproducir con precisión en un "micro-benchmark". IOW, al final, si realmente le importa el rendimiento, debe comparar cuidadosamente (y, por separado, el perfil) su (s) programa (s) real (es), no solo los micro-puntos de referencia "de juguete", en el caso general.

+0

1+. También se puede observar que en muchos casos los generadores pueden usar menos memoria debido a su flujo como la naturaleza. Considere leer cada línea de un archivo en una lista y compararla con la lectura de cada línea, trabajar con ella y descartarla. – Skurmedel

3

Por lo que recuerdo, un marco de generador debe activarse para cada resultado, mientras que la comprensión de la lista utiliza el marco de activación. El costo incremental en la compresión de la lista es el costo adicional de la memoria: referencias a int en su caso. La relación puede invertirse si cada elemento es una nueva instancia y utiliza más memoria.

actualización: Después de las pruebas, se hizo revertir

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum([oint(1) for i in L if i & 1])" 
10 loops, best of 3: 414 msec per loop 

~% python -m timeit -s "L = xrange(1000000);oint=type('intEx', (int,),{})" "sum(oint(1) for i in L if i & 1)" 
10 loops, best of 3: 392 msec per loop 
Cuestiones relacionadas