Spec-Zone.ru › Python 3.13

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

автор:

Pablo Galindo

Профилировщик Linux perf — очень мощный инструмент, который позволяет профилировать приложение и получать информацию о его производительности. 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 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–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/perf_profiling.html

Spec-Zone.ru

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