Поддержка Python для профилировщика Linux perf
¶
- автор:
Пабло Галиндо
The Linux perf profiler - это очень мощный инструмент, позволяющий профилировать и получать информацию о производительности вашего приложения. perf
также имеет очень яркую экосистему инструментов, которые помогают анализировать данные, которые он производит.
Основная проблема при использовании профилировщика perf
с приложениями Python заключается в том, что perf
получает информацию только о родных символах, то есть именах функций и процедур, написанных на C. Это означает, что имена и названия файлов функций Python в вашем коде не появятся в выводе perf
.
Начиная с Python 3.12, интерпретатор может работать в специальном режиме, который позволяет функциям Python отображаться в выводе perf
профилировщика. Когда этот режим включен, интерпретатор вставляет небольшой фрагмент кода, скомпилированный на лету, перед выполнением каждой функции Python и обучает perf
взаимосвязи между этим фрагментом кода и связанной с ним функцией Python с помощью perf map files.
Примечание
Поддержка профилировщика perf
в настоящее время доступна только для Linux на некоторых архитектурах. Проверьте вывод шага сборки 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%--_PyLong_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%--_PyLong_Add
| | | | | |
| | | | | |--3.26%--_PyObject_Malloc
Как включить поддержку профилирования perf
¶
Поддержка профилирования 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 script.py
$ perf report -g -i perf.data
Пример, используя опцию -X
:
$ perf record -F 9999 -g -o perf.data python -X perf script.py
$ perf report -g -i perf.data
Пример, использующий sys
API в файле 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
Как добиться наилучших результатов¶
Для достижения наилучших результатов Python следует компилировать с CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
, поскольку это позволяет профилировщикам разворачивать код, используя только указатель кадра, а не отладочную информацию DWARF. Это связано с тем, что код, который вставляется для обеспечения поддержки perf
, генерируется динамически и не имеет доступной отладочной информации DWARF.
Вы можете проверить, была ли ваша система скомпилирована с этим флагом, выполнив команду:
$ python -m sysconfig | grep 'no-omit-frame-pointer'
Если вы не видите никакого вывода, это означает, что ваш интерпретатор не был скомпилирован с указателями кадров, и поэтому он может не показывать функции Python в выводе perf
.
Как работать без указателей кадров¶
Если вы работаете с интерпретатором Python, который был скомпилирован без указателей кадров, вы все равно можете использовать профилировщик perf
, но накладные расходы будут немного выше, потому что Python должен генерировать информацию о разворачивании для каждого вызова функции Python на лету. Кроме того, perf
потребует больше времени на обработку данных, поскольку для разворачивания стека ему придется использовать отладочную информацию DWARF, а это медленный процесс.
Чтобы включить этот режим, вы можете использовать переменную окружения PYTHON_PERF_JIT_SUPPORT
или опцию -X perf_jit
, которая включит режим JIT для профилировщика perf
.
Примечание
Из-за ошибки в инструменте perf
только версии perf
выше v6.8 будут работать в режиме JIT. Исправление также было перенесено в версию инструмента 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 --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 dump, который создает 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,4096
. Размер дампа стека очень важен, так как при слишком маленьком размере perf
не сможет развернуть стек и вывод будет неполным. С другой стороны, если размер будет слишком большим, то perf
не сможет выполнять выборку процесса так часто, как хотелось бы, поскольку накладные расходы будут выше.