Поддержка профилировщика perf для Linux в Python
- author:
-
Pablo Galindo
Профилировщик perf для Linux — очень мощный инструмент, который позволяет профилировать и получать информацию о производительности вашего приложения. perf также имеет очень активную экосистему инструментов, которые помогают с анализом данных, которые он генерирует.
Основная проблема использования профилировщика perf с приложениями Python заключается в том, что perf получает информацию только о нативных символах, то есть о именах функций и процедур, написанных на C. Это означает, что имена и имена файлов функций Python в вашем коде не будут отображаться в выводе perf.
Начиная с Python 3.12, интерпретатор может работать в специальном режиме, который позволяет отображать функции Python в выводе профилировщика perf. При включении этого режима интерпретатор вставит небольшой фрагмент кода, скомпилированный на лету, перед выполнением каждой функции Python, и он научит perf связи между этим фрагментом кода и соответствующей функцией Python, используя файлы perf map.
Примечание
Поддержка профилировщика 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
Пример, используя 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
Как получить лучшие результаты
Для достижения наилучших результатов 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, созданный Python (в /tmp/perf-$PID.dump), и затем создаст perf.jit.data, который объединяет всю информацию JIT вместе. Он также должен создать множество файлов jitted-XXXX-N.so в текущем каталоге, которые представляют собой ELF-образы для всех созданных Python JIT-трамплинов.
Предупреждение
Обратите внимание, что при использовании --call-graph dwarf инструмент perf будет делать снимки стека процесса, который профилируется, и сохранять информацию в файл perf.data. По умолчанию размер дампа стека составляет 8192 байта, но пользователь может изменить размер, передав размер после запятой, например, --call-graph dwarf,4096. Размер дампа стека важен, потому что если размер слишком мал, perf не сможет раскрутить стек, и вывод будет неполным. С другой стороны, если размер слишком большой, perf не сможет отслеживать процесс так часто, как хотелось бы, так как накладные расходы будут выше.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/perf_profiling.html