Новое в Python 3.12
Профилировщик производительности Linux (Linux perf profiler) - это очень мощный инструмент, который позволяет вам профилировать и получать информацию о производительности вашего приложения. perf
также обладает очень динамичной экосистемой инструментов, которые помогают в анализе получаемых данных.
Основная проблема при использовании Linux perf profiler с приложениями Python заключается в том, что perf
получает информацию только о собственных символах, то есть именах функций и процедур, написанных на C. Это означает, что имена функций Python и имена файлов в коде не будут отображаться в выходных данных perf
.
Начиная с Python 3.12, интерпретатор может запускаться в специальном режиме, который позволяет функциям Python отображаться в выходных данных perf profiler
. Когда этот режим включен, то интерпретатор вставляет небольшой фрагмент кода, скомпилированный "на лету", перед выполнением каждой функции Python и обучает perf
взаимосвязи между этим фрагментом кода и соответствующей функцией Python, используя файлы perf map
.
Примечание. Поддержка профилировщика производительности в настоящее время доступна только для Linux на некоторых архитектурах. Чтобы узнать, поддерживается ли система Linux perf profiler необходимо проверить выходные данные этапа сборки 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 не отображаются в выходных данных, отображается только _Py_Eval_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
может быть включена либо с самого начала, используя переменную окружения PYTHONPERFSUPPORT
или параметр командной строки Python -X perf
, либо динамически, используя sys.activate_stack_trampoline()
и sys.deactivate_stack_trampoline()
.
Функции модуля
sys
имеют приоритет над параметром-X
, параметр-X
имеет приоритет над переменной окруженияPYTHONPERFSUPPORT
.
Пример использования переменной окружения:
$ PYTHONPERFSUPPORT=1 python script.py # смотрим выходные данные $ perf report -g -i perf.data
Пример использования параметра командной строки Python -X
:
$ 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()
Затем смотрим выходные данные:
$ 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
.