Spec-Zone.ru › Python 3.14

Поддержка Python для профилировщика perf в Linux

автор:

Pablo Galindo

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

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

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

Примечание

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

Как добиться наилучших результатов

Для наилучших результатов 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 режим JIT работает только с версиями perf выше v6.8. Исправление также было перенесено в версию инструмента v6.7.2.

При проверке версии инструмента perf (это можно сделать командой perf version) учитывайте, что некоторые дистрибутивы добавляют собственные номера версий, в том числе символ -. Это означает, что perf 6.7-3 не обязательно является perf 6.7.3.

При использовании режима JIT perf перед запуском 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) обычно обеспечивают более надёжную раскрутку стека

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/perf_profiling.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API