perf_profiling.md
1> **Источник:** https://python-all.ru/3.15/howto/perf_profiling.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Поддержка Python для профилировщиков, совместимых с `perf map`89[Профилировщик Linux perf](https://python-all.ru/3.15/howto/perf_profiling.html) и [samply](https://python-all.ru/3.15/howto/perf_profiling.html) – мощные инструменты, позволяющие профилировать и получать информацию о производительности вашего приложения. Оба инструмента имеют развитые экосистемы, помогающие анализировать данные, которые они собирают.1011Основная проблема использования этих профилировщиков с приложениями Python заключается в том, что они получают информацию только о нативных символах, то есть об именах функций и процедур, написанных на C. Это означает, что имена и имена файлов функций Python в вашем коде не будут отображаться в выводе профилировщика.1213Начиная с Python 3.12, интерпретатор может работать в специальном режиме, который позволяет функциям Python отображаться в выводе совместимых профилировщиков. Когда этот режим включён, интерпретатор вставляет небольшой фрагмент кода, скомпилированный на лету, перед выполнением каждой функции Python, и сообщает профилировщику связь между этим фрагментом кода и соответствующей функцией Python с помощью [файлов perf map](https://python-all.ru/3.15/c-api/perfmaps.html).1415> **Примечание**16>17> Поддержка профилирования доступна на Linux и macOS для выбранных архитектур. Perf доступен на Linux, тогда как samply можно использовать и на Linux, и на macOS. Поддержка samply на macOS доступна начиная с Python 3.15. Проверьте вывод шага сборки `configure` или вывод `python -m sysconfig | grep HAVE_PERF_TRAMPOLINE`, чтобы узнать, поддерживается ли ваша система.1819Например, рассмотрим следующий скрипт:2021```python22def foo(n):23 result = 024 for _ in range(n):25 result += 126 return result2728def bar(n):29 foo(n)3031def baz(n):32 bar(n)3334if __name__ == "__main__":35 baz(1000000)36```3738Мы можем запустить `perf` для сбора выборок стеков ЦП с частотой 9999 Гц:3940```console41$ perf record -F 9999 -g -o perf.data python my_script.py42```4344Затем мы можем использовать `perf report` для анализа данных:4546```console47$ perf report --stdio -n -g4849# Children Self Samples Command Shared Object Symbol50# ........ ........ ............ .......... .................. ..........................................51#52 91.08% 0.00% 0 python.exe python.exe [.] _start53 |54 ---_start55 |56 --90.71%--__libc_start_main57 Py_BytesMain58 |59 |--56.88%--pymain_run_python.constprop.060 | |61 | |--56.13%--_PyRun_AnyFileObject62 | | _PyRun_SimpleFileObject63 | | |64 | | |--55.02%--run_mod65 | | | |66 | | | --54.65%--PyEval_EvalCode67 | | | _PyEval_EvalFrameDefault68 | | | PyObject_Vectorcall69 | | | _PyEval_Vector70 | | | _PyEval_EvalFrameDefault71 | | | PyObject_Vectorcall72 | | | _PyEval_Vector73 | | | _PyEval_EvalFrameDefault74 | | | PyObject_Vectorcall75 | | | _PyEval_Vector76 | | | |77 | | | |--51.67%--_PyEval_EvalFrameDefault78 | | | | |79 | | | | |--11.52%--_PyCompactLong_Add80 | | | | | |81 | | | | | |--2.97%--_PyObject_Malloc82...83```8485Как видите, функции Python не отображаются в выводе, только `_PyEval_EvalFrameDefault` (функция, вычисляющая байт-код Python). К сожалению, это не очень полезно, потому что все функции Python используют одну и ту же функцию C для вычисления байт-кода, поэтому мы не можем узнать, какая функция Python соответствует какой функции вычисления байт-кода.8687Вместо этого, если мы запустим тот же эксперимент с включённой поддержкой `perf`, мы получим:8889```console90$ perf report --stdio -n -g9192# Children Self Samples Command Shared Object Symbol93# ........ ........ ............ .......... .................. .....................................................................94#95 90.58% 0.36% 1 python.exe python.exe [.] _start96 |97 ---_start98 |99 --89.86%--__libc_start_main100 Py_BytesMain101 |102 |--55.43%--pymain_run_python.constprop.0103 | |104 | |--54.71%--_PyRun_AnyFileObject105 | | _PyRun_SimpleFileObject106 | | |107 | | |--53.62%--run_mod108 | | | |109 | | | --53.26%--PyEval_EvalCode110 | | | py::<module>:/src/script.py111 | | | _PyEval_EvalFrameDefault112 | | | PyObject_Vectorcall113 | | | _PyEval_Vector114 | | | py::baz:/src/script.py115 | | | _PyEval_EvalFrameDefault116 | | | PyObject_Vectorcall117 | | | _PyEval_Vector118 | | | py::bar:/src/script.py119 | | | _PyEval_EvalFrameDefault120 | | | PyObject_Vectorcall121 | | | _PyEval_Vector122 | | | py::foo:/src/script.py123 | | | |124 | | | |--51.81%--_PyEval_EvalFrameDefault125 | | | | |126 | | | | |--13.77%--_PyCompactLong_Add127 | | | | | |128 | | | | | |--3.26%--_PyObject_Malloc129```130131## Использование профилировщика samply132133samply – это современный профилировщик, который можно использовать как альтернативу perf. Он использует те же файлы perf map, которые генерирует Python, что делает его совместимым с поддержкой профилирования в Python. samply особенно полезен на macOS, где perf недоступен.134135Чтобы использовать samply с Python, сначала установите его, следуя инструкциям на [https://github.com/mstange/samply](https://python-all.ru/3.15/howto/perf_profiling.html), а затем выполните:136137```console138$ samply record PYTHONPERFSUPPORT=1 python my_script.py139```140141Это откроет веб-интерфейс, в котором можно интерактивно анализировать данные профилирования. Преимущество samply в том, что он предоставляет современный веб-интерфейс для анализа данных профилирования и работает как на Linux, так и на macOS.142143На macOS поддержка samply требует Python 3.15 или новее. Также на macOS samply не может профилировать подписанные исполняемые файлы Python из-за ограничений macOS. Вы можете профилировать с помощью двоичных файлов Python, которые вы скомпилировали сами, или которые не подписаны или подписаны локально (например, всё, что установлено через Homebrew). Чтобы подключаться к запущенным процессам на macOS, выполните `samply setup` один раз (и каждый раз при обновлении samply), чтобы самоподписать двоичный файл samply.144145## Как включить поддержку профилирования `perf`146147Поддержку профилирования `perf` можно включить либо с самого начала, используя переменную окружения [`PYTHONPERFSUPPORT`](https://python-all.ru/3.15/using/cmdline.html#envvar-PYTHONPERFSUPPORT) или параметр [`-X perf`](https://python-all.ru/3.15/using/cmdline.html#cmdoption-X), либо динамически с помощью [`sys.activate_stack_trampoline()`](https://python-all.ru/3.15/library/sys.html#sys.activate_stack_trampoline) и [`sys.deactivate_stack_trampoline()`](https://python-all.ru/3.15/library/sys.html#sys.deactivate_stack_trampoline).148149Функции `sys` имеют приоритет над параметром `-X`, а параметр `-X` имеет приоритет над переменной окружения.150151Пример с использованием переменной окружения:152153```console154$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python my_script.py155$ perf report -g -i perf.data156```157158Пример с использованием параметра `-X`:159160```console161$ perf record -F 9999 -g -o perf.data python -X perf my_script.py162$ perf report -g -i perf.data163```164165Пример с использованием API [`sys`](https://python-all.ru/3.15/library/sys.html#module-sys) в файле `example.py`:166167```python168import sys169170sys.activate_stack_trampoline("perf")171do_profiled_stuff()172sys.deactivate_stack_trampoline()173174non_profiled_stuff()175```176177…затем:178179```console180$ perf record -F 9999 -g -o perf.data python ./example.py181$ perf report -g -i perf.data182```183184## Как получить наилучшие результаты185186Для достижения наилучших результатов оставляйте указатели фреймов включёнными. В поддерживаемых GCC-совместимых цепочках инструментов CPython собирается с флагами `-fno-omit-frame-pointer` и подобными (см. [`--without-frame-pointers`](https://python-all.ru/3.15/using/configure.html#cmdoption-without-frame-pointers) для подробностей). Эти флаги позволяют профилировщикам раскручивать стек, используя только указатель фрейма, а не отладочную информацию DWARF. Это связано с тем, что код, который вставляется для поддержки `perf`, генерируется динамически и не имеет доступной отладочной информации DWARF.187188Вы можете проверить, скомпилирована ли ваша система с этим флагом, выполнив:189190```console191$ python -m sysconfig | grep 'no-omit-frame-pointer'192```193194Если вы не видите никакого вывода, это означает, что ваш интерпретатор не был скомпилирован с указателями фреймов, и поэтому он может не отображать функции Python в выводе `perf`.195196## Как работать без указателей на фреймы197198Если работа ведётся с интерпретатором Python, скомпилированным без указателей на фреймы, всё равно можно использовать профилировщик `perf`, но накладные расходы будут немного выше, поскольку Python'у необходимо на лету генерировать информацию для раскрутки стека при каждом вызове функции Python. Кроме того, `perf` потребуется больше времени на обработку данных, так как придётся использовать отладочную информацию DWARF для раскрутки стека, а это медленный процесс.199200Чтобы включить этот режим, можно использовать переменную окружения [`PYTHON_PERF_JIT_SUPPORT`](https://python-all.ru/3.15/using/cmdline.html#envvar-PYTHON_PERF_JIT_SUPPORT) или опцию [`-X perf_jit`](https://python-all.ru/3.15/using/cmdline.html#cmdoption-X), которая включает режим JIT для профилировщика `perf`.201202> **Примечание**203>204> Из-за ошибки в инструменте `perf` с режимом JIT работают только версии `perf` выше v6.8. Исправление также было перенесено в версию v6.7.2 этого инструмента.205>206> Обратите внимание: при проверке версии инструмента `perf` (что можно сделать, запустив `perf version`) необходимо учитывать, что некоторые дистрибутивы добавляют собственные номера версий, включая символ `-`. Это означает, что `perf 6.7-3` не обязательно равно `perf 6.7.3`.207208При использовании режима perf JIT необходимо выполнить дополнительный шаг перед запуском `perf report`. Нужно вызвать команду `perf inject`, чтобы внедрить информацию JIT в файл `perf.data`:209210```console211$ perf record -F 9999 -g -k 1 --call-graph dwarf -o perf.data python -Xperf_jit my_script.py212$ perf inject -i perf.data --jit --output perf.jit.data213$ perf report -g -i perf.jit.data214```215216или используя переменную окружения:217218```console219$ PYTHON_PERF_JIT_SUPPORT=1 perf record -F 9999 -g --call-graph dwarf -o perf.data python my_script.py220$ perf inject -i perf.data --jit --output perf.jit.data221$ perf report -g -i perf.jit.data222```223224Команда `perf inject --jit` прочитает `perf.data`, автоматически подхватит файл дампа perf, создаваемый Python (в `/tmp/perf-$PID.dump`), и затем создаст `perf.jit.data`, в котором объединяется вся информация JIT. Она также должна создать множество файлов `jitted-XXXX-N.so` в текущем каталоге, которые являются ELF-образами для всех трамплинов JIT, созданных Python.225226> **Предупреждение**227>228> При использовании `--call-graph dwarf` инструмент `perf` будет делать снимки стека профилируемого процесса и сохранять информацию в файле `perf.data`. По умолчанию размер дампа стека составляет 8192 байта, но его можно изменить, передав размер после запятой, например `--call-graph dwarf,16384`.229>230> Размер дампа стека важен: если он слишком мал, `perf` не сможет раскрутить стек, и вывод будет неполным. С другой стороны, если размер слишком велик, `perf` не сможет производить выборку процесса так часто, как хотелось бы, поскольку накладные расходы будут выше.231>232> Размер стека особенно важен при профилировании кода Python, скомпилированного с низким уровнем оптимизации (например, `-O0`), поскольку такие сборки обычно имеют более крупные стековые фреймы. Если при компиляции Python с `-O0` в выводе профилировщика не видно функций Python, попробуйте увеличить размер дампа стека до 65528 байт (максимум):233>234> ```console235> $ perf record -F 9999 -g -k 1 --call-graph dwarf,65528 -o perf.data python -Xperf_jit my_script.py236> ```237>238> Разные флаги компиляции могут существенно влиять на размер стека:239>240> - Сборки с `-O0` обычно имеют гораздо более крупные стековые фреймы, чем сборки с `-O1` или выше.241> - Добавление оптимизаций (`-O1`, `-O2` и т.д.) обычно уменьшает размер стека.242> - Указатели на фреймы (`-fno-omit-frame-pointer`) обычно обеспечивают более надёжную раскрутку стека.243