versiones guia español descargar actualizar python python-2.7 generator

python - guia - qgis manual



¿Por qué un generador que usa `()` necesita mucha memoria? (2)

  1. Como otros han señalado en los comentarios, el range crea una list en Python 2. Por lo tanto, no es el generador per se el que usa la memoria, sino el range 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

  2. 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 de start , stop y step ):

    x = (i**2 for i in xrange(20000000))

    En Python 3, el range es esencialmente lo que solía ser xrange en Python 2. Sin embargo, el objeto Python 3 tiene algunas características xrange el xrange Python 2 no tiene, como O(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:

  1. ¿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?
  2. ¿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))