Как понять, какая часть системы сколько времени занимает с помощью профайлера
Что проверяют
Умеете ли вы найти узкое место измерением, а не догадкой, и читать вывод профайлера. Обычно следом просят объяснить разницу между tottime и cumtime или спрашивают, как профилировать сервис, который уже работает в продакшене.
Как отвечать
Сначала разделите два класса профайлеров:
- детерминированные:
cProfileиз стандартной библиотеки перехватывает каждый вызов функции. Точно считает вызовы, но замедляет программу и на коде из множества мелких функций искажает картину; - сэмплирующие:
py-spy, Scalene, pyinstrument снимают стек через равные интервалы. Накладные расходы низкие, аpy-spyподключается к работающему процессу без перезапуска:py-spy top --pid 1234илиpy-spy recordс выводом flame graph.
Запуск cProfile: python -m cProfile -o out.prof app.py, затем pstats с сортировкой или визуализация в snakeviz. Ключевые колонки:
ncalls: сколько раз вызвана функция. Сто тысяч вызовов дешёвой функции часто важнее одного медленного;tottime: время внутри самой функции без вложенных вызовов, показывает, где реально тратится CPU;cumtime: время вместе со всем, что функция вызвала, показывает, какая ветка программы дорогая.
Если горячая функция найдена, но непонятно, какая строка виновата, подключают line_profiler: декоратор @profile и запуск через kernprof -l -v. Во flame graph ширина полосы означает долю времени, и широкие плато наверху стека это кандидаты на оптимизацию.
Для веб-сервиса помните, что время часто уходит не в CPU, а в ожидание: базу, сеть, блокировки. Трассировка запросов через OpenTelemetry или APM покажет это лучше, чем CPU-профиль.
Где спотыкаются
Профилируют один прогон на игрушечных данных. Узкое место на ста строках и на миллионе часто разное, нужны реалистичный объём и прогрев.
Смотрят только на cumtime и «оптимизируют» main, у которой эта колонка всегда близка к полному времени. Искать надо по tottime и по числу вызовов.
Профилируют асинхронный код через cProfile и не понимают вывод: корутина, ждущая I/O, выглядит просто долгим вызовом, а время размазано по event loop. Для asyncio удобнее pyinstrument с поддержкой async или py-spy.