slicing operator python memory-management performance benchmarking

operator - Uso de memoria Python Slice Assignment



slice vector python (1)

Leí en un comentario aquí en Desbordamiento de pila que es más eficiente en la memoria hacer la asignación de porciones al cambiar las listas. Por ejemplo,

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

debería ser más eficiente en memoria que

a = [i + 6 for i in a]

porque el primero reemplaza los elementos en la lista existente, mientras que el segundo crea una nueva lista y vuelve a enlazar a esa nueva lista, dejando la antigua a en la memoria hasta que se pueda recolectar la basura. Comparando los dos para la velocidad, este último 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 esperaría, ya que volver a vincular una variable debería ser más rápido que reemplazar elementos en una lista. Sin embargo, no puedo encontrar ninguna documentación oficial que respalde la afirmación de uso de memoria, y no estoy seguro de cómo comparar eso.

A primera vista, el uso de memoria tiene sentido para mí. Sin embargo, si lo pienso un poco más, esperaría que en el método anterior, el intérprete creara una nueva lista de la lista de comprensión y luego copiara los valores de esa lista a a , dejando la lista anónima flotando hasta que sea basura. recogido. Si ese es el caso, entonces el método anterior usaría la misma cantidad de memoria y al mismo tiempo sería más lento.

¿Alguien puede mostrar de manera definitiva (con una referencia o documentación oficial) cuál de los dos métodos es más eficiente en memoria / cuál es el método preferido?

Gracias por adelantado.


La línea

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

No guardaría ningún recuerdo. Python primero evalúa el lado derecho, como se indica en la documentación del idioma :

Una declaración de asignación evalúa la lista de expresiones (recuerde que esto puede ser una expresión única o una lista separada por comas; esta última produce 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 destino sería a[:] .

Podríamos reemplazar la lista de comprensión por una expresión generadora:

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

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

a = [i + 6 for i in a]

Entonces, ¿la expresión del generador realmente guarda alguna memoria? A primera vista, podría pensar que lo hace. Pero profundizar en el código fuente de la función list_ass_slice() muestra que no lo hace. La línea

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

utiliza PySequence_Fast() para convertir PySequence_Fast() el iterable (en este caso, el generador) en una tupla, que luego se copia en la lista anterior. Una tupla usa la misma cantidad de memoria que una lista, por lo que usar una expresión generadora 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 reutilizan.

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