Документация Python неофициальный перевод

perf_profiling.md

233 строк · 17.3 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.14/howto/perf_profiling.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Поддержка профилировщика `perf` в Linux средствами Python89**автор:**1011Pablo Galindo1213[Профилировщик perf в Linux](https://python-all.ru/3.14/howto/perf_profiling.html) – это очень мощный инструмент, позволяющий профилировать и получать информацию о производительности вашего приложения. `perf` также обладает очень живой экосистемой инструментов, помогающих анализировать данные, которые он генерирует.1415Основная проблема использования профилировщика `perf` с приложениями Python заключается в том, что `perf` получает информацию только о нативных символах, то есть именах функций и процедур, написанных на C. Это означает, что имена и имена файлов функций Python в вашем коде не будут отображаться в выводе `perf`.1617Since Python 3.12, the interpreter can run in a special mode that allows Python functions to appear in the output of the `perf` profiler. When this mode is enabled, the interpreter will interpose a small piece of code compiled on the fly before the execution of every Python function and it will teach `perf` the relationship between this piece of code and the associated Python function using [perf map files](https://python-all.ru/3.14/c-api/perfmaps.html).1819> **Примечание**20>21> Поддержка профилировщика `perf` в настоящее время доступна только для Linux на отдельных архитектурах. Проверьте вывод шага сборки `configure` или вывод `python -m sysconfig | grep HAVE_PERF_TRAMPOLINE`, чтобы узнать, поддерживается ли ваша система.2223Например, рассмотрим следующий скрипт:2425```python26def foo(n):27    result = 028    for _ in range(n):29        result += 130    return result3132def bar(n):33    foo(n)3435def baz(n):36    bar(n)3738if __name__ == "__main__":39    baz(1000000)40```4142Мы можем запустить `perf` для сбора выборок стеков ЦП с частотой 9999 Гц:4344```console45$ perf record -F 9999 -g -o perf.data python my_script.py46```4748Затем мы можем использовать `perf report` для анализа данных:4950```console51$ perf report --stdio -n -g5253# Children      Self       Samples  Command     Shared Object       Symbol54# ........  ........  ............  ..........  ..................  ..........................................55#56    91.08%     0.00%             0  python.exe  python.exe          [.] _start57            |58            ---_start59            |60                --90.71%--__libc_start_main61                        Py_BytesMain62                        |63                        |--56.88%--pymain_run_python.constprop.064                        |          |65                        |          |--56.13%--_PyRun_AnyFileObject66                        |          |          _PyRun_SimpleFileObject67                        |          |          |68                        |          |          |--55.02%--run_mod69                        |          |          |          |70                        |          |          |           --54.65%--PyEval_EvalCode71                        |          |          |                     _PyEval_EvalFrameDefault72                        |          |          |                     PyObject_Vectorcall73                        |          |          |                     _PyEval_Vector74                        |          |          |                     _PyEval_EvalFrameDefault75                        |          |          |                     PyObject_Vectorcall76                        |          |          |                     _PyEval_Vector77                        |          |          |                     _PyEval_EvalFrameDefault78                        |          |          |                     PyObject_Vectorcall79                        |          |          |                     _PyEval_Vector80                        |          |          |                     |81                        |          |          |                     |--51.67%--_PyEval_EvalFrameDefault82                        |          |          |                     |          |83                        |          |          |                     |          |--11.52%--_PyLong_Add84                        |          |          |                     |          |          |85                        |          |          |                     |          |          |--2.97%--_PyObject_Malloc86...87```8889Как видите, функции Python не отображаются в выводе, только `_PyEval_EvalFrameDefault` (функция, вычисляющая байт-код Python). К сожалению, это не очень полезно, потому что все функции Python используют одну и ту же функцию C для вычисления байт-кода, поэтому мы не можем узнать, какая функция Python соответствует какой функции вычисления байт-кода.9091Вместо этого, если мы запустим тот же эксперимент с включённой поддержкой `perf`, мы получим:9293```console94$ perf report --stdio -n -g9596# Children      Self       Samples  Command     Shared Object       Symbol97# ........  ........  ............  ..........  ..................  .....................................................................98#99    90.58%     0.36%             1  python.exe  python.exe          [.] _start100            |101            ---_start102            |103                --89.86%--__libc_start_main104                        Py_BytesMain105                        |106                        |--55.43%--pymain_run_python.constprop.0107                        |          |108                        |          |--54.71%--_PyRun_AnyFileObject109                        |          |          _PyRun_SimpleFileObject110                        |          |          |111                        |          |          |--53.62%--run_mod112                        |          |          |          |113                        |          |          |           --53.26%--PyEval_EvalCode114                        |          |          |                     py::<module>:/src/script.py115                        |          |          |                     _PyEval_EvalFrameDefault116                        |          |          |                     PyObject_Vectorcall117                        |          |          |                     _PyEval_Vector118                        |          |          |                     py::baz:/src/script.py119                        |          |          |                     _PyEval_EvalFrameDefault120                        |          |          |                     PyObject_Vectorcall121                        |          |          |                     _PyEval_Vector122                        |          |          |                     py::bar:/src/script.py123                        |          |          |                     _PyEval_EvalFrameDefault124                        |          |          |                     PyObject_Vectorcall125                        |          |          |                     _PyEval_Vector126                        |          |          |                     py::foo:/src/script.py127                        |          |          |                     |128                        |          |          |                     |--51.81%--_PyEval_EvalFrameDefault129                        |          |          |                     |          |130                        |          |          |                     |          |--13.77%--_PyLong_Add131                        |          |          |                     |          |          |132                        |          |          |                     |          |          |--3.26%--_PyObject_Malloc133```134135## Как включить поддержку профилирования `perf`136137Поддержку профилирования `perf` можно включить либо с самого начала, используя переменную окружения [`PYTHONPERFSUPPORT`](https://python-all.ru/3.14/using/cmdline.html#envvar-PYTHONPERFSUPPORT) или параметр [`-X perf`](https://python-all.ru/3.14/using/cmdline.html#cmdoption-X), либо динамически с помощью [`sys.activate_stack_trampoline()`](https://python-all.ru/3.14/library/sys.html#sys.activate_stack_trampoline) и [`sys.deactivate_stack_trampoline()`](https://python-all.ru/3.14/library/sys.html#sys.deactivate_stack_trampoline).138139Функции `sys` имеют приоритет над параметром `-X`, а параметр `-X` имеет приоритет над переменной окружения.140141Пример с использованием переменной окружения:142143```console144$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python my_script.py145$ perf report -g -i perf.data146```147148Пример с использованием параметра `-X`:149150```console151$ perf record -F 9999 -g -o perf.data python -X perf my_script.py152$ perf report -g -i perf.data153```154155Пример с использованием API [`sys`](https://python-all.ru/3.14/library/sys.html#module-sys) в файле `example.py`:156157```python158import sys159160sys.activate_stack_trampoline("perf")161do_profiled_stuff()162sys.deactivate_stack_trampoline()163164non_profiled_stuff()165```166167…затем:168169```console170$ perf record -F 9999 -g -o perf.data python ./example.py171$ perf report -g -i perf.data172```173174## Как получить наилучшие результаты175176Для достижения наилучших результатов Python следует компилировать с `CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"`, так как это позволяет профилировщикам выполнять размотку стека, используя только указатель кадра, а не отладочную информацию DWARF. Это связано с тем, что код, вставляемый для поддержки `perf`, генерируется динамически и не имеет доступной отладочной информации DWARF.177178Вы можете проверить, скомпилирована ли ваша система с этим флагом, выполнив:179180```console181$ python -m sysconfig | grep 'no-omit-frame-pointer'182```183184Если вы не видите никакого вывода, это означает, что ваш интерпретатор не был скомпилирован с указателями фреймов, и поэтому он может не отображать функции Python в выводе `perf`.185186## Как работать без указателей на фреймы187188Если работа ведётся с интерпретатором Python, скомпилированным без указателей на фреймы, всё равно можно использовать профилировщик `perf`, но накладные расходы будут немного выше, поскольку Python'у необходимо на лету генерировать информацию для раскрутки стека при каждом вызове функции Python. Кроме того, `perf` потребуется больше времени на обработку данных, так как придётся использовать отладочную информацию DWARF для раскрутки стека, а это медленный процесс.189190Чтобы включить этот режим, можно использовать переменную окружения [`PYTHON_PERF_JIT_SUPPORT`](https://python-all.ru/3.14/using/cmdline.html#envvar-PYTHON_PERF_JIT_SUPPORT) или опцию [`-X perf_jit`](https://python-all.ru/3.14/using/cmdline.html#cmdoption-X), которая включает режим JIT для профилировщика `perf`.191192> **Примечание**193>194> Из-за ошибки в инструменте `perf` с режимом JIT работают только версии `perf` выше v6.8. Исправление также было перенесено в версию v6.7.2 этого инструмента.195>196> Обратите внимание: при проверке версии инструмента `perf` (что можно сделать, запустив `perf version`) необходимо учитывать, что некоторые дистрибутивы добавляют собственные номера версий, включая символ `-`. Это означает, что `perf 6.7-3` не обязательно равно `perf 6.7.3`.197198При использовании режима perf JIT необходимо выполнить дополнительный шаг перед запуском `perf report`. Нужно вызвать команду `perf inject`, чтобы внедрить информацию JIT в файл `perf.data`:199200```console201$ perf record -F 9999 -g -k 1 --call-graph dwarf -o perf.data python -Xperf_jit my_script.py202$ perf inject -i perf.data --jit --output perf.jit.data203$ perf report -g -i perf.jit.data204```205206или используя переменную окружения:207208```console209$ PYTHON_PERF_JIT_SUPPORT=1 perf record -F 9999 -g --call-graph dwarf -o perf.data python my_script.py210$ perf inject -i perf.data --jit --output perf.jit.data211$ perf report -g -i perf.jit.data212```213214Команда `perf inject --jit` прочитает `perf.data`, автоматически подхватит файл дампа perf, создаваемый Python (в `/tmp/perf-$PID.dump`), и затем создаст `perf.jit.data`, в котором объединяется вся информация JIT. Она также должна создать множество файлов `jitted-XXXX-N.so` в текущем каталоге, которые являются ELF-образами для всех трамплинов JIT, созданных Python.215216> **Предупреждение**217>218> При использовании `--call-graph dwarf` инструмент `perf` будет делать снимки стека профилируемого процесса и сохранять информацию в файле `perf.data`. По умолчанию размер дампа стека составляет 8192 байта, но его можно изменить, передав размер после запятой, например `--call-graph dwarf,16384`.219>220> Размер дампа стека важен: если он слишком мал, `perf` не сможет раскрутить стек, и вывод будет неполным. С другой стороны, если размер слишком велик, `perf` не сможет производить выборку процесса так часто, как хотелось бы, поскольку накладные расходы будут выше.221>222> Размер стека особенно важен при профилировании кода Python, скомпилированного с низким уровнем оптимизации (например, `-O0`), поскольку такие сборки обычно имеют более крупные стековые фреймы. Если при компиляции Python с `-O0` в выводе профилировщика не видно функций Python, попробуйте увеличить размер дампа стека до 65528 байт (максимум):223>224> ```console225> $ perf record -F 9999 -g -k 1 --call-graph dwarf,65528 -o perf.data python -Xperf_jit my_script.py226> ```227>228> Разные флаги компиляции могут существенно влиять на размер стека:229>230> - Сборки с `-O0` обычно имеют гораздо более крупные стековые фреймы, чем сборки с `-O1` или выше.231> - Добавление оптимизаций (`-O1`, `-O2` и т.д.) обычно уменьшает размер стека.232> - Указатели на фреймы (`-fno-omit-frame-pointer`) обычно обеспечивают более надёжную раскрутку стека.233