ЗАМЕР
bench/iterators/memory_time.py
Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.
- Цитируется в статье
- /ru/interview/python/iterators-and-generators
- Как запустить
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v protocol.py; done
Запись прогона
Замеры для урока «Итераторы и генераторы»
| скрипт | что показывает |
|---|---|
protocol.py |
iter(x) is x для списка и итератора, исчерпание, состояния генератора |
silent.py |
ошибка, которая не падает: два прохода по аргументу дают 0 вместо 19 |
memory_time.py |
пик памяти (tracemalloc) и время для списка против генератора на 1 000 000 и на 10 элементах |
traps.py |
PEP 479 (RuntimeError и __cause__), yield from против ручного цикла, in и len на генераторе |
versions.py |
запись __future__.generator_stop и возврат значения из close() |
inlining.py |
PEP 709 структурно: <listcomp>, MAKE_FUNCTION и глубина стека — без секундомера |
Запускать на всех версиях, которые есть:
for v in 3.11 3.12 3.13 3.14; do echo "== $v"; python$v protocol.py; done
Практика урока
practice.py — источник ответов двух практических задач урока, а runs/practice.txt —
дословная запись его прогона. Ответ задачи не сочиняется: сборка сверяет
заявленное с этой записью (scripts/validate-practice.mjs) и не проходит,
если они разошлись.
Прогон снят 30.08.2026 на CPython 3.13.7 (Clang 20.1.4). Абсолютные числа — этой машины; переносится кратность, и задача «во сколько раз» стоит именно на ней.
Кратность устойчива только потому, что формы меряются ВПЕРЕМЕЖКУ: в каждом круге меряются все, минимум для каждой берётся по кругам. Пока замеры шли подряд, просадка машины в окне одной формы целиком доставалась ей, и отношение гуляло в полтора раза от запуска к запуску (измерено на декораторах: 5,9 / 7,1 / 7,5 / 9,2). После перехода на чередование расхождение между прогонами не выходит за несколько процентов. Перезаписывать запись прогона имеет смысл только вместе с проверкой задачи: если после перезапуска ответ изменился, менять нужно задачу, а не файл.
Правило, из-за которого половина замеров сделана структурно
3.11 и 3.12 собраны GCC 13.3.0, а 3.13 и 3.14 — Clang 20.1.4. Сравнивать время между этими парами нельзя: разница сборок перекрывает разницу версий; это общее правило замеров, а не оговорка одного этого каталога.
Поэтому эффект PEP 709 в уроке показан не секундомером, а inlining.py:
наличием объекта кода <listcomp>, инструкции MAKE_FUNCTION и глубиной
стека внутри включения. Эти признаки от тулчейна не зависят вовсе.
Сравнение «генератор против списка» при этом остаётся честным всегда: оно делается внутри одного запуска одного интерпретатора.
Скрипт
32 строкimport sys, tracemalloc, timeit
print("PY", sys.version.split()[0])
N = 1_000_000
def peak(make):
tracemalloc.start()
obj = make()
total = sum(obj) if not isinstance(obj, list) else sum(obj)
cur, pk = tracemalloc.get_traced_memory()
tracemalloc.stop()
return pk, total
pk_list, _ = peak(lambda: [i * i for i in range(N)])
pk_gen, _ = peak(lambda: (i * i for i in range(N)))
print(f"память на {N:,} элементов: список {pk_list/1024/1024:7.2f} МиБ | генератор {pk_gen/1024:7.1f} КиБ")
setup = "N = 1_000_000"
t_list = min(timeit.repeat("sum([i*i for i in range(N)])", setup=setup, repeat=5, number=3)) / 3
t_gen = min(timeit.repeat("sum(i*i for i in range(N))", setup=setup, repeat=5, number=3)) / 3
print(f"время sum по {N:,}: список {t_list*1000:7.1f} мс | генератор {t_gen*1000:7.1f} мс"
f" | генератор /{t_gen/t_list:.2f}x")
# стоимость создания одного генератора против списка на маленьких данных
t_mk_gen = min(timeit.repeat("(i for i in r)", setup="r=range(10)", repeat=7, number=200_000)) / 200_000
t_mk_lst = min(timeit.repeat("[i for i in r]", setup="r=range(10)", repeat=7, number=200_000)) / 200_000
print(f"создать на 10 элементах: генератор {t_mk_gen*1e9:6.0f} нс | список {t_mk_lst*1e9:6.0f} нс")
# полный обход маленькой последовательности
t_it_gen = min(timeit.repeat("sum(i for i in r)", setup="r=range(10)", repeat=7, number=200_000)) / 200_000
t_it_lst = min(timeit.repeat("sum([i for i in r])", setup="r=range(10)", repeat=7, number=200_000)) / 200_000
print(f"создать и просуммировать 10: генератор {t_it_gen*1e9:6.0f} нс | список {t_it_lst*1e9:6.0f} нс")