Подтвердить что ты не робот

Почему генератору с использованием `()` требуется много памяти?

Проблема

Предположим, что я хочу найти n**2 для всех чисел, меньших, чем 20000000.

Общая настройка для всех трех вариантов, которые я тестирую:

    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

Три параметра для расчета этих чисел:

1. Создание списка через понимание:

    x = [i**2 for i in range(20000000)]

Это очень медленно и требует много времени:

    Used Mem =  1270  # Megabytes
    Calculation time =  33.9309999943  # Seconds

2. Создание генератора с помощью '()':

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

Это намного быстрее, чем опция 1, но все еще использует много памяти:

    Used Mem =  611 
    Calculation time =  0.278000116348 

3. Определение функции генератора (наиболее эффективно):

    def f(n):
        i = 0
        while i < n:
            yield i**2
            i += 1
    x = f(20000000)

Потребление:

    Used Mem =  0
    Calculation time =  0.0

Вопросы:

  • Какая разница между первым и вторым решениями? Использование () создает генератор, поэтому зачем ему нужно много памяти?
  • Есть ли встроенная функция, эквивалентная моей третьей опции?
4b9b3361

Ответ 1

  • Как отмечают другие в комментариях, range создает list в Python 2. Следовательно, это не сам генератор, который использует память, а range, что генератор использует:

    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
    

    Это также объясняет, почему ваша вторая версия (выражение генератора) использует примерно половину памяти первой версии (понимание списка), поскольку первая строит два списка (для оснований и квадратов), а вторая только строит один список для баз.

  • xrange(20000000), таким образом, значительно улучшает использование памяти, так как возвращает ленивый итерируемый. Это, по сути, эффективный способ встроенной памяти для итерации по диапазону чисел, который отражает вашу третью версию (с добавленной гибкостью start, stop и step):

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

    В Python 3, range, по существу, используется xrange в Python 2. Тем не менее, у объекта Python 3 range есть некоторые приятные функции, которые Python 2 xrange не имеет, например, O(1) slicing, содержит и т.д.

Некоторые ссылки:

Ответ 2

1.- Объект должен быть создан в памяти, поэтому в вашем втором решении генератор создан, но не вычисляется, но все еще имеет память, python, вероятно, резервирует некоторую память для того, чтобы ее вычисление было эффективным, мы не знаем об магии интерпретатора, также замечаем, что range funtion создает полный список от 0 до 200000, так что на самом деле вы все еще строят этот список в памяти.

2.- Вы можете использовать itertool.imap:

squares = itertools.imap(lambda x: x**2, xrange(200000))