Choose “Save as PDF” in your browser’s print dialog to keep a copy.

Хвост инженера

Когда таблицы профайлера уже недостаточно

#Python#производительность

Автор: Кирилл Борисов · PyCon Russia 2016


Профилировщик легко находит функции, в которых программа проводит больше всего времени. Трудности начинаются после этого: таблица сообщает, что дорого, но часто не объясняет, как исполнение туда попало и почему этот путь оказался настолько частым.

Кирилл Борисов показывает, как sampling profiling превращает работу программы в набор снимков стека. Вместо инструментирования заранее выбранных функций профайлер периодически фиксирует весь текущий путь вызовов. Такой сбор пропускает короткие события и не даёт точной хронологии, зато меньше вмешивается в выполнение программы и сохраняет контекст: не только работающую функцию, но и цепочку, которая к ней привела.

Проблема в объёме. На реальном приложении быстро появляются тысячи и миллионы стеков, а плоский отчёт сводит их к отдельным строкам. Flame graph агрегирует одинаковые пути и превращает их в интерактивную карту. Ширина блока показывает, в какой доле сэмплов встречался соответствующий стек, а вертикаль сохраняет иерархию вызовов. Поэтому на графике можно увидеть не просто дорогую функцию, а разные маршруты, из которых она была вызвана.

Доклад записан в 2016 году: конкретные Python-инструменты и способы их подключения стоит воспринимать как исторический контекст. Сам подход не устарел — flame graph остаётся одним из основных представлений для sampling и continuous profiling.

Кому смотреть: разработчикам, которые уже запускали профайлер, получали длинную таблицу функций и не понимали, с какого места начинать расследование.

Из этого можно взять в работу: при следующем исследовании производительности сохранять не только суммарную стоимость функций, но и полные стеки вызовов — причина дорогого участка может находиться выше по пути исполнения.


Доклад начинается с различия между двумя способами профилирования. Инструментирование фиксирует заранее определённые события и способно дать подробную картину, но требует менять код или среду исполнения и само может повлиять на измерение. Sampling периодически останавливает программу на короткое время и записывает её текущее состояние. Полученная картина неполна, зато для длительных операций статистической выборки обычно достаточно.

На маленьком примере оба подхода выглядят управляемыми. На production-сервисе профилировщик собирает стеки с множества процессов и машин, после чего разработчик остаётся с огромным массивом строк. Простое суммирование по функциям уменьшает объём данных, но уничтожает часть ответа: одна функция могла быть вызвана из нескольких совершенно разных сценариев.

Flame graph отбрасывает временной порядок сэмплов, сортирует стеки по общим предкам и объединяет одинаковые пути. Поэтому горизонтальная ось — не шкала времени. Широкий блок означает, что функция вместе с этим контекстом вызова часто встречалась в выборке. Чем выше блок, тем глубже соответствующий фрейм находился в стеке.

Борисов демонстрирует подход на небольшом сервисе сокращения ссылок. На графике виден общий путь через веб-сервер и фреймворк, после которого исполнение расходится по обработчикам. В одной из веток обнаруживается неожиданно заметный путь обработки ошибок базы данных и откатов. Автор не собирался искать этот дефект, но сохранённый контекст позволил заметить его и перейти к конкретному участку программы. Точный процент из устной демонстрации лучше не абсолютизировать: важен сам факт, что ошибочный путь оказался достаточно широким, чтобы выделиться на общем графике.

При этом flame graph не ставит диагноз. Самый широкий блок не обязан быть ошибкой, а короткая, но критичная операция может не попасть в выборку. График нужен, чтобы сформулировать следующий вопрос: почему программа так часто проходит именно этим путём и соответствует ли это ожидаемому поведению?

Сегодня тот же принцип используется далеко за пределами показанного Python-модуля. Изменились профайлеры, способы сбора стеков и интерфейсы, появились continuous profiling и eBPF-инструменты. Не изменилась исходная задача: превратить массу измерений в представление, где видно не только потребление ресурсов, но и структуру исполнения программы.

Original post: https://territory.network/@engineers-tail/191/kogda-tablitsy-profailera-uzhe-nedostatochno