python - guia - qgis manual
¿Por qué un generador que usa `()` necesita mucha memoria? (2)
Como otros han señalado en los comentarios, el
range
crea unalist
en Python 2. Por lo tanto, no es el generador per se el que usa la memoria, sino elrange
que utiliza el generador:x = (i**2 for i in range(20000000)) # builds a 2*10**7 element list, not for the squares , but for the bases >>> sys.getsizeof(range(100)) 872 >>> sys.getsizeof(xrange(100)) 40 >>> sys.getsizeof(range(1000)) 8720 >>> sys.getsizeof(xrange(1000)) 40 >>> sys.getsizeof(range(20000000)) 160000072 >>> sys.getsizeof(xrange(20000000)) 40
Esto también explica por qué su segunda versión (la expresión del generador) usa aproximadamente la mitad de la memoria de la primera versión (la lista de comprensión) ya que la primera construye dos listas (para las bases y los cuadrados) mientras que la segunda solo construye una lista para el bases
xrange(20000000)
tanto, mejora en gran medida el uso de la memoria, ya que devuelve un iterable perezosa. Esta es esencialmente la forma eficiente de memoria integrada para iterar en un rango de números que refleja su tercera versión (con la flexibilidad añadida destart
,stop
ystep
):x = (i**2 for i in xrange(20000000))
En Python 3, el
range
es esencialmente lo que solía serxrange
en Python 2. Sin embargo, el objeto Python 3 tiene algunas característicasxrange
elxrange
Python 2 no tiene, comoO(1)
slicing, contains, etc.
Algunas referencias:
- Python2 xrange docs
- Python3 range docs
- Stack Overflow - "¿Debería siempre favorecer a xrange () sobre el rango ()?"
- La excelente respuesta de Martijn Pieters a "¿Por qué está 1000000000000000 en el rango (1000000000000001) tan rápido en Python 3?"
Problema
Supongamos que quiero encontrar n**2
para todos los números menores a 20000000
.
Configuración general para las tres variantes que pruebo:
import time, psutil, gc
gc.collect()
mem_before = psutil.virtual_memory()[3]
time1 = time.time()
# (comprehension, generator, function)-code comes here
time2 = time.time()
mem_after = psutil.virtual_memory()[3]
print "Used Mem = ", (mem_after - mem_before)/(1024**2) # convert Byte to Megabyte
print "Calculation time = ", time2 - time1
Tres opciones para calcular estos números:
1. Creando una lista de a través de la comprensión:
x = [i**2 for i in range(20000000)]
Es realmente lento y consume mucho tiempo:
Used Mem = 1270 # Megabytes
Calculation time = 33.9309999943 # Seconds
2. Creando un generador usando ''()''
:
x = (i**2 for i in range(20000000))
Es mucho más rápido que la opción 1, pero aún usa mucha memoria:
Used Mem = 611
Calculation time = 0.278000116348
3. Definición de una función de generador (más eficiente):
def f(n):
i = 0
while i < n:
yield i**2
i += 1
x = f(20000000)
Su consumo:
Used Mem = 0
Calculation time = 0.0
Las preguntas son:
- ¿Cuál es la diferencia entre la primera y la segunda solución? El uso de
()
crea un generador, entonces, ¿por qué necesita mucha memoria? - ¿Hay alguna función incorporada equivalente a mi tercera opción?
1.- El objeto debe ser creado en la memoria, entonces en su segunda solución, el generador se crea pero no se computa , pero aún tiene memoria, es probable que python se reserve algo de memoria para que su computación sea eficiente, no sabemos sobre el intérprete magia, también observe que range
funtion crea la lista completa de 0
a 200000
, por lo que de hecho todavía está creando esa lista en la memoria.
2.- Puedes usar itertool.imap :
squares = itertools.imap(lambda x: x**2, xrange(200000))