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

Поддержка профилировщика perf в Linux средствами PythonPython support for the Linux perf profiler

автор:

Pablo Galindo

Профилировщик perf в Linux – это очень мощный инструмент, позволяющий профилировать и получать информацию о производительности вашего приложения. perf также обладает очень живой экосистемой инструментов, помогающих анализировать данные, которые он генерирует.

Основная проблема использования профилировщика perf с приложениями Python заключается в том, что perf получает информацию только о нативных символах, то есть именах функций и процедур, написанных на C. Это означает, что имена и имена файлов функций Python в вашем коде не будут отображаться в выводе perf.

Since 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.

Примечание

Поддержка профилировщика 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

Как включить поддержку профилирования perfHow to enable perf profiling support

Поддержку профилирования perf можно включить либо с самого начала, используя переменную окружения PYTHONPERFSUPPORT или параметр -X perf, либо динамически с помощью sys.activate_stack_trampoline() и sys.deactivate_stack_trampoline().

Функции sys имеют приоритет над параметром -X, а параметр -X имеет приоритет над переменной окружения.

Пример с использованием переменной окружения:

$ PYTHONPERFSUPPORT=1 python script.py
$ perf report -g -i perf.data

Пример с использованием параметра -X:

$ 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()

…затем:

$ python ./example.py
$ perf report -g -i perf.data

Как получить наилучшие результатыHow to obtain the best results

Для достижения наилучших результатов Python следует компилировать с CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer", так как это позволяет профилировщикам выполнять размотку стека, используя только указатель кадра, а не отладочную информацию DWARF. Это связано с тем, что код, вставляемый для поддержки perf, генерируется динамически и не имеет доступной отладочной информации DWARF.

Вы можете проверить, скомпилирована ли ваша система с этим флагом, выполнив:

$ python -m sysconfig | grep 'no-omit-frame-pointer'

Если вы не видите никакого вывода, это означает, что ваш интерпретатор не был скомпилирован с указателями фреймов, и поэтому он может не отображать функции Python в выводе perf.