Comments 16
Странный пример. Второй вариант и не считал ничего. Попробуйте хотя бы сумму квадратов посчитать. Тогда и будет видно, что дает генератор. Вот память он в этом случае сэкономит, это да, так как все числа хранить не будет. Или я не прав?
Там ещё и не квадраты чисел — а проверка на чётность.
Но это уже вопрос к автору оригинала, там тоже речь про квадраты — а код про чётность. Индус, которому код писал другой индус?
Как мы видим, время выполнения
Имхо, в данном случае некорректно сравнивать время выполнения, т. к. в случае с генератором мы никаких вычислений не произвели
и сохранить квадраты всех чисел, которые нужно хранить отдельно в другом списке
Также и тут, в случае с генератором мы не храним квадраты всех чисел в отдельном списке, мы можем получить слудующий квадрат при необходимости.
Как мне кажется лучше было бы добавить:
for i in cubes:
do_something()
так сравнение будет более честным
Примерно так:
def get_data_from_big_file():
with open('big_file.txt', 'r', encoding='utf-8') as f:
while True:
line = f.readline()
if not line:
break
yield line
Серьёзно? "Не учите детей плохому!" (с)
Для чего создавать отдельную генератор-функцию, внутри которой изобретать велосипед с прерываемым бесконечным циклом, если абсолютно ту же функциональность даёт встроенный open
:
for line in open('big_file.txt', 'r', encoding='utf-8'):
# process line
Плюс при объяснениях таких простых тем необходимо, для новичков, уточнять что это для текстовых файлов, а функция с именем начинающимся с get_
не возвращает data_from_big_file
, а возвращает генератор.
Генераторы работают по принципу, известному как «ленивые вычисления». Это значит, что они могут экономить ресурсы процессора, памяти и других вычислительных ресурсов.
Нет, это значит, что они производят вычисления (и тратят "ресурсы процессора, памяти и других вычислительных ресурсов") по мере необходимости, а не наперед вне зависимости от того, нужен ли кому-то результат этих вычислений.
И экономят ресурсы ленивые вычисления только в том случае, когда результаты вычислений не запрашиваются (частично, или, как в статье, совсем). Если же используются все результаты, то ленивые впечатления ничего не сэкономят. В лучшем случае они лишь отложат использование такого-же количества ресурсов. А иногда даже наоборот — добавят накладных расходов.
П.С. Уточню, что последний абзац справедлив только для случаев, когда ленивые и не-ленивые вычисления действительно делают одно и то же. Если не-ленивый вариант помимо вычисления значений еще и занят складыванием их в массивы и выделением памяти под все это добро, то очевидно, в этом случае он действительно может потреблять больше ресурсов, чем его ленивый собрат.
Нет, это не так. При создании объекта внутри объекта никаких вычислений не производится, а впервые интерпретатор зайдет внутрь лишь при первом next. Именно поэтому требуется при создании корутин первым вызовом всегда делать next или send(None). И, соответственно, потому останавливается сразу ПОСЛЕ инструкции yield, а не до.
Упоминали что ещё один студент остался с переводами на хабр, но походу незачет ;)
В случае с функцией вы выполняете вычисление квадратов и сохраняете их в список. В случае же с генератором вы просто создали объект типа генератор, не вызывая в этом случае вычисления. Поэтому сравнивать время и память в данном случае будет неправильно.
Генераторы будут эффективны только в том случае, если нам не нужны все значения сразу (либо не все значения вообще будут задействованы). Если же нам нужно работать со всеми значениями вычисленными в теле функции (генератора) сразу, то в этом случае функция может оказаться даже более эффективной по времени выполнения.
В комментариях много раз упоминали, что тест скорости неверный — генератор ничего не делает. Посмотрим же на истинные результаты. Для этого исправим код — мы будем не просто получать все квадраты ЧЁТНЫХ (привет автору) чисел, а будем вычислять их общую сумму.
Первый тест без генератора:
# python3 -m pip install memory_profiler
import memory_profiler
import time
def check_even(numbers):
even = []
for num in numbers:
if num % 2 == 0:
even.append(num * num)
return even
if __name__ == '__main__':
number = 10**8
m1 = memory_profiler.memory_usage()
t1 = time.clock()
cubes = check_even(range(number))
sum_cubes = sum(cubes)
m2 = memory_profiler.memory_usage()
t2 = time.clock()
time_diff = t2 - t1
mem_diff = m2[0] - m1[0]
print(f"It took {time_diff} Secs and {mem_diff} Mb to calculate\n" +
f"the sum of squares of first {number} even numbers (it is {sum_cubes})")
Результат:
It took 22.652704714 Secs and 1936.12109375 Mb to calculate
the sum of squares of first 100000000 even numbers (it is 166666661666666700000000)
Причём заметим, что сначала я отдельно вычислил все значения, а уже потом посчитал их сумму и записал в новую переменную. Если сделать это в одну строку sum_cubes = sum(check_even(range(number))), то замер используемой памяти покажет всего 1.109375 Mb использованной памяти. Но по факту будет использованы все те же 2 ГБ памяти для вычислений списка квадратов — он просто будет сразу же уничтожен. Поэтому здесь может показаться, что память используется мало — это ошибочное предположение. При number = 10**9 мой тест завершается с MemoryError (переполнение памяти).
А вот тест с генератором:
# python3 -m pip install memory_profiler
import memory_profiler
import time
def check_even(numbers):
for num in numbers:
if num % 2 == 0:
yield num * num
if __name__ == '__main__':
number = 10**8
m1 = memory_profiler.memory_usage()
t1 = time.clock()
sum_cubes = sum(check_even(range(number)))
m2 = memory_profiler.memory_usage()
t2 = time.clock()
time_diff = t2 - t1
mem_diff = m2[0] - m1[0]
print(f"It took {time_diff} Secs and {mem_diff} Mb to calculate\n" +
f"the sum of squares of first {number} even numbers (it is {sum_cubes})")
Результат:
It took 17.981277267 Secs and 0.04296875 Mb to calculate
the sum of squares of first 100000000 even numbers (it is 166666661666666700000000)
Подводим итог: с генератором скорость увеличилась на 15%, при этом памяти использовалось почти на 2 ГБ меньше.
А вот результаты теста с генератором при number == 10**9 (в 10 раз больше, чем в первоначальном тесте):
It took 182.005517055 Secs and 0.078125 Mb to calculate
the sum of squares of first 1000000000 even numbers (it is 166666666166666667000000000)
без генератора вообще получим переполнение памяти
Как уменьшить использование памяти и ускорить работу кода на Python с помощью генераторов