2011-02-09 21 views
18

Leí en un comentario aquí en Stack Overflow que es más eficiente en la memoria hacer la asignación de división cuando se cambian las listas. Por ejemplo,Python Slice Assignment Memory Usage

a[:] = [i + 6 for i in a] 

debe ser más eficiente de la memoria de

a = [i + 6 for i in a] 

porque el primero sustituye a los elementos de la lista existente, mientras que el segundo crea una nueva lista y vuelve a enlazar a a la nueva lista, dejando el viejo a en la memoria hasta que pueda ser basura recolectada. Evaluación comparativa de los dos para la velocidad, éste es un poco más rápido:

$ python -mtimeit -s 'a = [1, 2, 3]' 'a[:] = [i + 6 for i in a]' 
1000000 loops, best of 3: 1.53 usec per loop 
$ python -mtimeit -s 'a = [1, 2, 3]' 'a = [i + 6 for i in a]' 
1000000 loops, best of 3: 1.37 usec per loop 

Eso es lo que cabe esperar, como revinculación de una variable debe ser más rápido que la sustitución de los elementos en una lista. Sin embargo, no puedo encontrar ninguna documentación oficial que respalde el reclamo de uso de memoria, y no estoy seguro de cómo compararlo.

A primera vista, el reclamo de uso de memoria tiene sentido para mí. Sin embargo, pensándolo mejor, esperaría que en el método anterior, el intérprete creara una nueva lista de la lista comprensiva y luego copiara los valores de esa lista a a, dejando la lista anónima flotando hasta que es basura recolectada Si ese es el caso, entonces el método anterior usaría la misma cantidad de memoria y también sería más lento.

¿Alguien puede mostrar definitivamente (con un punto de referencia o documentación oficial) cuál de los dos métodos es más eficiente con la memoria/cuál es el método preferido?

Gracias de antemano.

+1

Merece la pena considerar los aspectos de rendimiento, pero creo que es más probable que te topes con el caso práctico (en programas más grandes) donde pasas una referencia a una lista, por ejemplo, de Class1 a Class2. En la primera instancia, usar la asignación de división para modificar la lista de Class1 conservará la referencia de Class2. En la segunda instancia que cita, la modificación de la lista de Class1 significa que Class2 tendrá una referencia a una lista que ya no es válida. – Brandon

+0

@Brandon: Eso también es cierto, y probablemente debería haber mencionado la distinción en mi pregunta. Gracias por tu contribución. –

Respuesta

40

La línea

a[:] = [i + 6 for i in a] 

no salvaría a cualquier memoria. Python hace evaluar el lado derecho en primer lugar, como se indica en la language documentation:

una instrucción de asignación evalúa la lista de expresiones (recuerde que esto puede ser una sola expresión o una lista separada por comas, este último dando una tupla) y asigna el único objeto resultante a cada una de las listas de destino, de izquierda a derecha.

En el caso que nos ocupa, el único objeto resultante sería una nueva lista, y el único objetivo en la lista de objetivos sería a[:].

Podríamos sustituir la lista comprensión por un generador de expresión:

a[:] = (i + 6 for i in a) 

Ahora, el lado derecho se evalúa como un generador en lugar de una lista. La evaluación comparativa muestra que esto sigue siendo más lento que los ingenuos

a = [i + 6 for i in a] 

también lo hace la expresión generadora de hecho guarda ningún recuerdo? A primera vista, podrías pensar que sí. Pero profundizar en el source code of the function list_ass_slice() muestra que no es así.La línea

v_as_SF = PySequence_Fast(v, "can only assign an iterable"); 

utiliza PySequence_Fast() para convertir el iterable (en este caso el generador) en una primera tupla, que se copia a continuación, en la lista de edad. Una tupla utiliza la misma cantidad de memoria que una lista, por lo que usar una expresión de generador es básicamente lo mismo que usar una lista de comprensión en este caso. Durante la última copia, los elementos de la lista original se vuelven a usar.

La moraleja parece ser que el enfoque más simple es el mejor en cualquier aspecto.

+4

+1 para la trituración sin piedad de los optimizadores prematuros (de memoria). – delnan

+0

¡Gracias por la respuesta detallada y perspicaz! En respuesta al comentario anterior, me gustaría añadir que esto podría no haber sido una optimización prematura si se tratara de una lista de 5 millones de elementos y tuviera la opción de copiarla o no copiarla. :) –

+1

@Mitch: si tienes 5 millones de entradas, probablemente estés mejor con, por ej. una matriz NumPy que una lista de Python. –