Содержание страницы
Поддержка Python для профилировщиков, совместимых с perf map¶Python support for the perf map compatible profilers
Профилировщик Linux perf и samply – мощные инструменты, позволяющие профилировать и получать информацию о производительности вашего приложения. Оба инструмента имеют развитые экосистемы, помогающие анализировать данные, которые они собирают.
Основная проблема использования этих профилировщиков с приложениями Python заключается в том, что они получают информацию только о нативных символах, то есть об именах функций и процедур, написанных на C. Это означает, что имена и имена файлов функций Python в вашем коде не будут отображаться в выводе профилировщика.
Начиная с Python 3.12, интерпретатор может работать в специальном режиме, который позволяет функциям Python отображаться в выводе совместимых профилировщиков. Когда этот режим включён, интерпретатор вставляет небольшой фрагмент кода, скомпилированный на лету, перед выполнением каждой функции Python, и сообщает профилировщику связь между этим фрагментом кода и соответствующей функцией Python с помощью файлов perf map.
Примечание
Поддержка профилирования доступна на Linux и macOS для выбранных архитектур.
Perf доступен на Linux, тогда как samply можно использовать и на Linux, и на macOS.
Поддержка samply на macOS доступна начиная с Python 3.15.
Проверьте вывод шага сборки configure или
вывод python -m sysconfig | grep HAVE_PERF_TRAMPOLINE,
чтобы узнать, поддерживается ли ваша система.
Например, рассмотрим следующий скрипт:
def foo(n):
result = 0
for _ in range(n):
result += 1
return result
def bar(n):
foo(n)
def baz(n):
bar(n)
if __name__ == "__main__":
baz(1000000)
Мы можем запустить perf для сбора выборок стеков ЦП с частотой 9999 Гц:
$ perf record -F 9999 -g -o perf.data python my_script.py
Затем мы можем использовать perf report для анализа данных:
$ perf report --stdio -n -g
# Children Self Samples Command Shared Object Symbol
# ........ ........ ............ .......... .................. ..........................................
#
91.08% 0.00% 0 python.exe python.exe [.] _start
|
---_start
|
--90.71%--__libc_start_main
Py_BytesMain
|
|--56.88%--pymain_run_python.constprop.0
| |
| |--56.13%--_PyRun_AnyFileObject
| | _PyRun_SimpleFileObject
| | |
| | |--55.02%--run_mod
| | | |
| | | --54.65%--PyEval_EvalCode
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | |
| | | |--51.67%--_PyEval_EvalFrameDefault
| | | | |
| | | | |--11.52%--_PyCompactLong_Add
| | | | | |
| | | | | |--2.97%--_PyObject_Malloc
...
Как видите, функции Python не отображаются в выводе, только _PyEval_EvalFrameDefault
(функция, вычисляющая байт-код Python). К сожалению, это не очень полезно, потому что все функции Python
используют одну и ту же функцию C для вычисления байт-кода, поэтому мы не можем узнать, какая функция Python соответствует какой
функции вычисления байт-кода.
Вместо этого, если мы запустим тот же эксперимент с включённой поддержкой perf, мы получим:
$ perf report --stdio -n -g
# Children Self Samples Command Shared Object Symbol
# ........ ........ ............ .......... .................. .....................................................................
#
90.58% 0.36% 1 python.exe python.exe [.] _start
|
---_start
|
--89.86%--__libc_start_main
Py_BytesMain
|
|--55.43%--pymain_run_python.constprop.0
| |
| |--54.71%--_PyRun_AnyFileObject
| | _PyRun_SimpleFileObject
| | |
| | |--53.62%--run_mod
| | | |
| | | --53.26%--PyEval_EvalCode
| | | py::<module>:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::baz:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::bar:/src/script.py
| | | _PyEval_EvalFrameDefault
| | | PyObject_Vectorcall
| | | _PyEval_Vector
| | | py::foo:/src/script.py
| | | |
| | | |--51.81%--_PyEval_EvalFrameDefault
| | | | |
| | | | |--13.77%--_PyCompactLong_Add
| | | | | |
| | | | | |--3.26%--_PyObject_Malloc
Использование профилировщика samply¶Using the samply profiler
samply – это современный профилировщик, который можно использовать как альтернативу perf. Он использует те же файлы perf map, которые генерирует Python, что делает его совместимым с поддержкой профилирования в Python. samply особенно полезен на macOS, где perf недоступен.
Чтобы использовать samply с Python, сначала установите его, следуя инструкциям на https://github.com/mstange/samply, а затем выполните:
$ samply record PYTHONPERFSUPPORT=1 python my_script.py
Это откроет веб-интерфейс, в котором можно интерактивно анализировать данные профилирования. Преимущество samply в том, что он предоставляет современный веб-интерфейс для анализа данных профилирования и работает как на Linux, так и на macOS.
На macOS поддержка samply требует Python 3.15 или новее. Также на macOS samply
не может профилировать подписанные исполняемые файлы Python из-за ограничений macOS. Вы можете
профилировать с помощью двоичных файлов Python, которые вы скомпилировали сами, или которые
не подписаны или подписаны локально (например, всё, что установлено через Homebrew). Чтобы
подключаться к запущенным процессам на macOS, выполните samply setup один раз (и
каждый раз при обновлении samply), чтобы самоподписать двоичный файл samply.
Как включить поддержку профилирования perf¶How to enable perf profiling support
Поддержку профилирования perf можно включить либо с самого начала, используя
переменную окружения PYTHONPERFSUPPORT или
параметр -X perf,
либо динамически с помощью sys.activate_stack_trampoline() и
sys.deactivate_stack_trampoline().
Функции sys имеют приоритет над параметром -X,
а параметр -X имеет приоритет над переменной окружения.
Пример с использованием переменной окружения:
$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python my_script.py
$ perf report -g -i perf.data
Пример с использованием параметра -X:
$ perf record -F 9999 -g -o perf.data python -X perf my_script.py
$ perf report -g -i perf.data
Пример с использованием API sys в файле example.py:
import sys
sys.activate_stack_trampoline("perf")
do_profiled_stuff()
sys.deactivate_stack_trampoline()
non_profiled_stuff()
…затем:
$ perf record -F 9999 -g -o perf.data python ./example.py
$ perf report -g -i perf.data
Как получить наилучшие результаты¶How to obtain the best results
Для достижения наилучших результатов оставляйте указатели фреймов включёнными. В поддерживаемых GCC-совместимых
цепочках инструментов CPython собирается с флагами -fno-omit-frame-pointer и подобными
(см. --without-frame-pointers для подробностей). Эти флаги позволяют
профилировщикам раскручивать стек, используя только указатель фрейма, а не отладочную информацию DWARF.
Это связано с тем, что код, который вставляется для поддержки perf,
генерируется динамически и не имеет доступной отладочной информации DWARF.
Вы можете проверить, скомпилирована ли ваша система с этим флагом, выполнив:
$ python -m sysconfig | grep 'no-omit-frame-pointer'
Если вы не видите никакого вывода, это означает, что ваш интерпретатор не был скомпилирован с
указателями фреймов, и поэтому он может не отображать функции Python в выводе
perf.
Как работать без указателей на фреймы¶How to work without frame pointers
Если работа ведётся с интерпретатором Python, скомпилированным без указателей на фреймы, всё равно можно использовать профилировщик perf, но накладные расходы будут немного выше, поскольку Python'у необходимо на лету генерировать информацию для раскрутки стека при каждом вызове функции Python. Кроме того, perf потребуется больше времени на обработку данных, так как придётся использовать отладочную информацию DWARF для раскрутки стека, а это медленный процесс.
Чтобы включить этот режим, можно использовать переменную окружения PYTHON_PERF_JIT_SUPPORT или опцию -X perf_jit, которая включает режим JIT для профилировщика perf.
Примечание
Из-за ошибки в инструменте perf с режимом JIT работают только версии perf выше v6.8. Исправление также было перенесено в версию v6.7.2 этого инструмента.
Обратите внимание: при проверке версии инструмента perf (что можно сделать, запустив perf version) необходимо учитывать, что некоторые дистрибутивы добавляют собственные номера версий, включая символ -. Это означает, что perf 6.7-3 не обязательно равно perf 6.7.3.
При использовании режима perf JIT необходимо выполнить дополнительный шаг перед запуском perf
report. Нужно вызвать команду perf inject, чтобы внедрить информацию JIT в файл perf.data:
$ perf record -F 9999 -g -k 1 --call-graph dwarf -o perf.data python -Xperf_jit my_script.py
$ perf inject -i perf.data --jit --output perf.jit.data
$ perf report -g -i perf.jit.data
или используя переменную окружения:
$ PYTHON_PERF_JIT_SUPPORT=1 perf record -F 9999 -g --call-graph dwarf -o perf.data python my_script.py
$ perf inject -i perf.data --jit --output perf.jit.data
$ perf report -g -i perf.jit.data
Команда perf inject --jit прочитает perf.data, автоматически подхватит файл дампа perf, создаваемый Python (в /tmp/perf-$PID.dump), и затем создаст perf.jit.data, в котором объединяется вся информация JIT. Она также должна создать множество файлов jitted-XXXX-N.so в текущем каталоге, которые являются ELF-образами для всех трамплинов JIT, созданных Python.
Предупреждение
При использовании --call-graph dwarf инструмент perf будет делать снимки стека профилируемого процесса и сохранять информацию в файле perf.data. По умолчанию размер дампа стека составляет 8192 байта, но его можно изменить, передав размер после запятой, например --call-graph dwarf,16384.
Размер дампа стека важен: если он слишком мал, perf не сможет раскрутить стек, и вывод будет неполным. С другой стороны, если размер слишком велик, perf не сможет производить выборку процесса так часто, как хотелось бы, поскольку накладные расходы будут выше.
Размер стека особенно важен при профилировании кода Python, скомпилированного с низким уровнем оптимизации (например, -O0), поскольку такие сборки обычно имеют более крупные стековые фреймы. Если при компиляции Python с -O0 в выводе профилировщика не видно функций Python, попробуйте увеличить размер дампа стека до 65528 байт (максимум):
$ perf record -F 9999 -g -k 1 --call-graph dwarf,65528 -o perf.data python -Xperf_jit my_script.py
Разные флаги компиляции могут существенно влиять на размер стека:
Сборки с
-O0обычно имеют гораздо более крупные стековые фреймы, чем сборки с-O1или выше.Добавление оптимизаций (
-O1,-O2и т.д.) обычно уменьшает размер стека.Указатели на фреймы (
-fno-omit-frame-pointer) обычно обеспечивают более надёжную раскрутку стека.