TGViewer
P(hD)ython P(hD)ython @phdython · 333 subscribers
Post #42 524
«Python для собесов - 2026 🚀»

#python

Продолжаем серию постов для Python-собесов!

В прошлых постах мы научились ускорять Python (FFI & Python Compilers). Теперь поговорим о том, что именно нужно ускорять, потому что оптимизировать абсолютно всё - сложно, долго и дорого (да зачастую и не нужно) 😵.

Базовый алгоритм выглядит следующим образом:
1. Замерили время выполнения кода.
2. Нашли узкое место.
3. Оптимизировали.
4. Проверили, что стало лучше. Если нет или недостаточно, то возвращаемся к пункту 1 😄.

Начнём с простейших измерений.

На собесах часто спрашивают как в коде замерить время выполнения отдельной функции. К сожалению, ответ time() встречается неприлично часто. Однако time() это так называемые «wall clock» 🕚 - они не монотонны (могут перепрыгивать из-за NTP, смены поясов, гибернации), а их точность и разрешение невысоки и зависят от настроек ОС. Вместо них лучше использовать монотонные счётчики:
- perf_counter_ns() - точный и монотонный аналог time() (для общих случаяев);
- process_time_ns() - только время на CPU (удобен для числодробилок).

Пример кода:

from time import perf_counter_ns as pc_ns

def work(n):
s = 0
for i in range(n):
s += i*i
return s

if __name__ == "__main__":
t = pc_ns()
work(1000000)
dt = pc_ns() - t
print(f"{dt/1e6:.3f} ms")


и его запуск:

python3 pc_ns_demo.py 
23.767 ms


Один прогон не слишком показателен - виной тому «шум» планировщика, кэши, вызовы GC и т.д. diff счётчиков можно сохранять в массив в цикле, но лучше использовать готовый инструмент - timeit. Он позволяет запускать много раз функции или даже программы, задавая как число прогонов в замере (number), так и число самих замеров (repeat), тем самым получая устойчивое распределение времени.

Пример кода:

import timeit
from pc_ns_demo import work

rs = timeit.repeat(lambda: work(1000000), number=10, repeat=10)

print("runs:")
for i, t in enumerate(rs, 1):
print(f" {i:02d}: {t/10*1e3:.3f} ms")

bs = min(rs)
print(f"min: {bs/10*1e3:.3f} ms")


и его вызов:

python3 timeit_demo.py 
runs:
01: 23.849 ms
...
10: 23.650 ms
min: 23.650 ms


Ещё короче:

python3 -m timeit -n 10 -r 10 -u msec -s "from pc_ns_demo import work" "work(1000000)"
10 loops, best of 10: 23.5 msec per loop


Поговорим о профилировании - определении конкретных медленных участков кода с cProfile. Он позволяет подсчитать число вызовов каждой функции (ncalls), её целевое и суммарное время исполнения (без/с учётом вызовов функций внутри) (tottime, percall, cumtime, percall) с привязкой к определению (filename:...).

Запуск профайлера для программы:

python3 -m cProfile -o prof.bin pc_ns_demo.py
24.467 ms

python -c "import pstats;p=pstats.Stats('prof.bin');p.sort_stats('tottime').print_stats(20)"
Fri Dec 19 06:00:17 2025 prof.bin

7 function calls in 0.024 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.024 0.024 0.024 0.024 pc_ns_demo.py:3(work)
...


Файл prof.bin можно визуализировать пакетом snakeviz - скрин в комментах.

И внутри python-кода:

import cProfile
import pstats
from io import StringIO
from pc_ns_demo import work

def kek(a, b, c):
return work(a) + work(b) + work(c)

pr = cProfile.Profile()
pr.enable()
print(kek(1000000, 2000000, 3000000))
pr.disable()

s = StringIO()
pstats.Stats(pr, stream=s).sort_stats("cumtime").print_stats(5)
print(s.getvalue())


и запуск:

python3 cprofile_demo.py
11999993000001000000
6 function calls in 0.146 seconds

Ordered by: cumulative time

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.146 0.146 /home/mikhail/cprofile_demo.py:6(kek)
3 0.146 0.049 0.146 0.049 /home/mikhail/pc_ns_demo.py:3(work)
...


Существуют и другие инструменты вроде py-spy и scalene. Они позволяют периодически дампить callstack процесса, а scalene показывает ещё и аллокации 🤓. Вот мы и познакомились с джентльменским набором тайминга и профайлинга в Python! ⚙️🚀

С уважением,
Масягин Михаил
  • 🔥 8
  • 👍 4
  • 💯 3
  • ⚡ 1
  • ❤ 1
More from @phdython
  1. Aug 25, 2026«Конференции и паровозики 🚂» #заметки_с_полей Отгремела «Профсоюзная 2.0» - конфа прошла…
  2. Aug 22, 2026Post #81
  3. Aug 20, 2026«Профсоюз выдвинул 😎» #заметки_с_полей 22 августа в Москве буду спикером на «Профсоюзной…
  4. Aug 16, 2026«Ну вот, я и стал админом 🧑‍💻» #техно_и_хардкор Собрал, казалось бы, банальный, но для м…
  5. Aug 11, 2026«Предзащита: больно и неофициально 🎓» #аспирантские_будни Наконец дошли руки до обещанног…
  6. Jul 18, 2026«Work-life balance от Anthropic ⚒️» #ai4sdlc Недельные лимиты токенов израсходованы - можн…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →