вызов функции python с/без понимания списка

У меня ниже двух функций:

def foo(n=50000):
    return sum(i*i for i in range(n))  # just called sum() directly without 

def bar(n=50000):
    return sum([i*i for i in range(n)])  # passed constructed list to sum()

Я надеялся, что foo будет работать быстрее, чем bar но я проверил в ipython с %%timeit что foo занимает немного больше времени, чем bar

In [2]: %%timeit
   ...: foo(50000)
   ...: 
100 loops, best of 3: 4.22 ms per loop

In [3]: %%timeit
   ...: bar(50000)
   ...: 
100 loops, best of 3: 3.45 ms per loop
In [4]: %%timeit
   ...: foo(10000000)
   ...: 
1 loops, best of 3: 1.02 s per loop

In [5]: %%timeit
   ...: bar(10000000)
   ...: 
1 loops, best of 3: 869 ms per loop

Разница увеличивается по мере увеличения значения n, поэтому я попытался проверить функцию с dis.dis(foo) и dis.dis(bar) но она была идентичной.

Итак, какова будет причина такой разницы во времени между обоими методами?

Ответ 1

Есть много отличных ответов на генераторы, поэтому я не буду подробно останавливаться на этом.

Генераторы сохраняют состояние. Они немного медленнее, если вы выполняете очень быстрые операции (например, используя sum, но если вы используете команду ввода-вывода, то не будет большой разницы). Потенциал роста генераторов заключается в том, что они не загружают все элементы в память заранее, когда списки выполняются.

Это то, что происходит, когда вы перебираете список (на очень высоком уровне):

  • Вы загружаете все элементы списка в память
  • Запрос следующего элемента просто дает вам указатель на этот объект

Сравните это с генератором:

  • У вас нет всех предметов в памяти. Только по одному пункту за раз.
  • Запрос следующего элемента возобновляет объект-генератор, запустив код, пока он не достигнет инструкции yield.
  • Затем он возвращает адрес объекта в памяти, чтобы вы могли получить к нему доступ.

Этот дополнительный шаг посередине - это разница в ваших тестах.

Таким образом, генераторы обычно используются там, где вы имеете дело с огромным объемом данных, которые необходимо загрузить в память. (Существует больше случаев использования генераторов, например, сопрограмм)

Выполняйте истечение с огромными файлами и цикл для печати строк. В какой-то момент вы получите исключение из памяти при использовании списков. Затем попробуйте использовать генераторы, они не исчезнут из памяти.