Pull to refresh

Comments 16

Странный пример. Второй вариант и не считал ничего. Попробуйте хотя бы сумму квадратов посчитать. Тогда и будет видно, что дает генератор. Вот память он в этом случае сэкономит, это да, так как все числа хранить не будет. Или я не прав?

Там ещё и не квадраты чисел — а проверка на чётность.


Но это уже вопрос к автору оригинала, там тоже речь про квадраты — а код про чётность. Индус, которому код писал другой индус?

Мы же считаем num*num, т.е. квадраты четных чисел — только и всего.

Для второго примера стоит добавить реальный обход генератора, иначе цифры по времени и памяти бессмысленны.
Вы во втором примере просто создали генератор, не получив из него ни одного значения, и уж тем более не «сохранили квадраты всех чисел, которые нужно хранить отдельно в другом списке». Естественно, и время выполнения было меньше, и память не занята.
Как мы видим, время выполнения

Имхо, в данном случае некорректно сравнивать время выполнения, т. к. в случае с генератором мы никаких вычислений не произвели
и сохранить квадраты всех чисел, которые нужно хранить отдельно в другом списке

Также и тут, в случае с генератором мы не храним квадраты всех чисел в отдельном списке, мы можем получить слудующий квадрат при необходимости.

Как мне кажется лучше было бы добавить:
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, а возвращает генератор.

Генераторы работают по принципу, известному как «ленивые вычисления». Это значит, что они могут экономить ресурсы процессора, памяти и других вычислительных ресурсов.

Нет, это значит, что они производят вычисления (и тратят "ресурсы процессора, памяти и других вычислительных ресурсов") по мере необходимости, а не наперед вне зависимости от того, нужен ли кому-то результат этих вычислений.
И экономят ресурсы ленивые вычисления только в том случае, когда результаты вычислений не запрашиваются (частично, или, как в статье, совсем). Если же используются все результаты, то ленивые впечатления ничего не сэкономят. В лучшем случае они лишь отложат использование такого-же количества ресурсов. А иногда даже наоборот — добавят накладных расходов.


П.С. Уточню, что последний абзац справедлив только для случаев, когда ленивые и не-ленивые вычисления действительно делают одно и то же. Если не-ленивый вариант помимо вычисления значений еще и занят складыванием их в массивы и выделением памяти под все это добро, то очевидно, в этом случае он действительно может потреблять больше ресурсов, чем его ленивый собрат.

UFO just landed and posted this here

Нет, это не так. При создании объекта внутри объекта никаких вычислений не производится, а впервые интерпретатор зайдет внутрь лишь при первом next. Именно поэтому требуется при создании корутин первым вызовом всегда делать next или send(None). И, соответственно, потому останавливается сразу ПОСЛЕ инструкции yield, а не до.

UFO just landed and posted this here

Упоминали что ещё один студент остался с переводами на хабр, но походу незачет ;)

Данное сравнение времени и памяти совершенно неверное.

В случае с функцией вы выполняете вычисление квадратов и сохраняете их в список. В случае же с генератором вы просто создали объект типа генератор, не вызывая в этом случае вычисления. Поэтому сравнивать время и память в данном случае будет неправильно.

Генераторы будут эффективны только в том случае, если нам не нужны все значения сразу (либо не все значения вообще будут задействованы). Если же нам нужно работать со всеми значениями вычисленными в теле функции (генератора) сразу, то в этом случае функция может оказаться даже более эффективной по времени выполнения.

В комментариях много раз упоминали, что тест скорости неверный — генератор ничего не делает. Посмотрим же на истинные результаты. Для этого исправим код — мы будем не просто получать все квадраты ЧЁТНЫХ (привет автору) чисел, а будем вычислять их общую сумму.


Первый тест без генератора:


# 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)

без генератора вообще получим переполнение памяти

Sign up to leave a comment.