> **Источник:** https://python-all.ru/3.15/library/profiling.sampling.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# `profiling.sampling` – Статистический профилировщик

Добавлено в версии 3.15.

**Исходный код:** [Lib/profiling/sampling/](https://python-all.ru/src/3.15/Lib/profiling/sampling)

---

[![Логотип Tachyon](https://python-all.ru/3.15/_images/tachyon-logo.png)](https://python-all.ru/3.15/_images/tachyon-logo.png)

Модуль `profiling.sampling`, называемый **Tachyon**, предоставляет статистическое профилирование программ Python с помощью периодической выборки стека. Tachyon может запускать скрипты напрямую или подключаться к любому работающему процессу Python без необходимости изменять код или перезапускать. Поскольку выборка происходит извне целевого процесса, накладные расходы практически нулевые, что делает Tachyon подходящим как для разработки, так и для рабочей среды.

## Что такое статистическое профилирование?

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

Этот подход основан на простом принципе: функции, потребляющие значительное время процессора, будут часто появляться в собранных образцах. Собирая тысячи образцов за сеанс профилирования, Tachyon строит точную статистическую оценку того, где тратится время. Чем больше образцов собрано, тем точнее становится эта оценка.

Следующая интерактивная визуализация демонстрирует, как работает профилирование с выборкой. Нажмите **Play**, чтобы наблюдать выполнение программы Python, и посмотрите, как профилировщик периодически захватывает снимки стека вызовов. Измените **интервал выборки**, чтобы увидеть, как частота выборки влияет на результаты.

### Как оценивается время

Значения времени, отображаемые в выводе Tachyon, – это **оценки, полученные из количества образцов**, а не прямые измерения. Tachyon подсчитывает, сколько раз каждая функция появляется в собранных образцах, а затем умножает на интервал выборки для оценки времени.

Например, при частоте выборки 10 кГц в течение 10-секундного профиля Tachyon собирает приблизительно 100 000 образцов. Если функция появляется в 5 000 образцов (5% от общего числа), Tachyon оценивает, что она потребляла 5% от 10-секундной длительности, или около 500 миллисекунд. Это статистическая оценка, а не точное измерение.

Точность этих оценок зависит от количества образцов. При 100 000 образцов функция, показывающая 5%, имеет погрешность примерно ±0,5%. При всего 1 000 образцов то же измерение 5% может фактически представлять от 3% до 7% реального времени.

Вот почему большая длительность профилирования и более короткие интервалы выборки дают более надежные результаты – они собирают больше образцов. Для большинства задач анализа производительности настройки по умолчанию обеспечивают достаточную точность для выявления узких мест и определения направлений оптимизации.

Поскольку выборка является статистической, результаты будут незначительно различаться между запусками. Функция, показывающая 12% в одном запуске, может показать 11% или 13% в следующем. Это нормально и ожидаемо. Сосредоточьтесь на общей картине, а не на точных процентах, и не беспокойтесь о небольших расхождениях между запусками.

### Когда использовать другой подход

Статистическая выборка подходит не для любой ситуации.

Для очень коротких скриптов, которые завершаются менее чем за секунду, профилировщик может не собрать достаточно образцов для надежных результатов. Вместо этого используйте [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) или запустите скрипт в цикле, чтобы увеличить время профилирования.

Когда вам нужно точное количество вызовов, выборка не может его предоставить. Выборка оценивает частоту по снимкам, поэтому, если вам нужно точно знать, сколько раз была вызвана функция, используйте [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

При сравнении двух реализаций, где разница может составлять всего 1-2%, шум выборки может скрыть реальные различия. Используйте [`timeit`](https://python-all.ru/3.15/library/timeit.html#module-timeit) для микробенчмарков или [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) для точных измерений.

Ключевое отличие от [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) заключается в том, как происходит измерение. Трассировочный профилировщик инструментирует ваш код, записывая каждый вызов функции и возврат. Это обеспечивает точное количество вызовов и точное время, но добавляет накладные расходы на каждый вызов функции. Профилировщик с выборкой, напротив, наблюдает за программой извне через фиксированные интервалы, не изменяя её выполнение. Представьте разницу так: трассировка – это как если бы кто-то следовал за вами и записывал каждый ваш шаг, а выборка – это как делать фотографии каждую секунду и восстанавливать ваш путь по этим снимкам.

Эта модель внешнего наблюдения делает профилирование с выборкой практичным для использования в рабочей среде. Профилируемая программа работает на полной скорости, потому что внутри неё не выполняется код инструментирования, и целевой процесс никогда не останавливается и не приостанавливается во время выборки – Tachyon читает стек вызовов непосредственно из памяти процесса, пока он продолжает выполняться. Вы можете подключиться к работающему серверу, собрать данные и отключиться, не давая приложению узнать, что за ним наблюдали. Оборотная сторона в том, что очень короткие функции могут быть пропущены, если они успевают завершиться между выборками.

Статистическое профилирование отлично подходит для ответа на вопрос: «Где моя программа тратит время?» Оно выявляет горячие точки и узкие места в рабочем коде, где накладные расходы детерминированного профилирования были бы неприемлемы. Для точного количества вызовов и полных графов вызовов используйте [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

## Быстрые примеры

Профилировать скрипт и сразу увидеть результаты:

```sh
python -m profiling.sampling run script.py
```

Профилировать модуль с аргументами:

```sh
python -m profiling.sampling run -m mypackage.module arg1 arg2
```

Создать интерактивный флейм-граф:

```sh
python -m profiling.sampling run --flamegraph -o profile.html script.py
```

Подключиться к работающему процессу по PID:

```sh
python -m profiling.sampling attach 12345
```

Вывести один снимок стека работающего процесса:

```sh
python -m profiling.sampling dump 12345
```

Использовать режим реального времени для мониторинга (нажмите `q` для выхода):

```sh
python -m profiling.sampling run --live script.py
```

Профилировать в течение 60 секунд с более высокой частотой выборки:

```sh
python -m profiling.sampling run -d 60 -r 20khz script.py
```

Создать построчную тепловую карту:

```sh
python -m profiling.sampling run --heatmap script.py
```

Включение профилирования на уровне опкодов, чтобы видеть, какие инструкции байт-кода выполняются:

```sh
python -m profiling.sampling run --opcodes --flamegraph script.py
```

## Команды

Tachyon работает через несколько подкоманд. `run` и `attach` собирают семплы с течением времени; `dump` делает единичный снимок; `replay` преобразует бинарные профили в другие форматы.

### Команда `run`

Команда `run` запускает Python-скрипт или модуль и профилирует его с момента запуска:

```sh
python -m profiling.sampling run script.py
python -m profiling.sampling run -m mypackage.module
```

При профилировании скрипта профилировщик запускает цель в подпроцессе, ждёт её инициализации, а затем начинает сбор семплов. Флаг `-m` указывает, что цель должна быть запущена как модуль (эквивалентно `python -m`). Аргументы после цели передаются профилируемой программе:

```sh
python -m profiling.sampling run script.py --config settings.yaml
```

### Команда `attach`

Команда `attach` подключается к уже запущенному процессу Python по его идентификатору процесса:

```sh
python -m profiling.sampling attach 12345
```

Эта команда особенно полезна для исследования проблем производительности в рабочих системах. Целевой процесс не требует модификации и не требует перезапуска. Профилировщик подключается, собирает семплы в течение заданного времени, затем отключается и формирует результат.

```sh
python -m profiling.sampling attach --live 12345
python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345
```

На большинстве систем для подключения к другому процессу требуются соответствующие разрешения. См. [Требования к платформе](https://python-all.ru/3.15/library/profiling.sampling.html#profiling-permissions) для уточнения требований для конкретной платформы.

### Команда `dump`

Команда `dump` выводит единичный снимок стека Python работающего процесса и завершается, аналогично traceback:

```sh
python -m profiling.sampling dump 12345
```

В отличие от `attach`, `dump` не выполняет цикл семплирования: он читает стек один раз. Это полезно для изучения зависших или неотвечающих процессов, а также для ответа на вопрос «что сейчас делает этот процесс?».

Вывод повторяет формат traceback (самый последний вызов вверху) и помечает каждый поток его текущим состоянием (главный поток, владеет GIL, на процессоре, ожидает GIL, есть исключение или бездействует):

```text
Stack dump for PID 12345, thread 140735 (main thread, has GIL, on CPU; most recent call last):
  File "server.py", line 28, in serve
    await handle_request(req)
  File "handler.py", line 91, in handle_request
    result = expensive_call(req)
```

Когда исходные файлы цели доступны для чтения, `dump` выводит строку исходного кода для каждого фрейма и подсвечивает выполняемое выражение.

Как и `attach`, `dump` требует разрешения на чтение памяти целевого процесса. См. [Требования к платформе](https://python-all.ru/3.15/library/profiling.sampling.html#profiling-permissions).

Команда `dump` поддерживает следующие параметры:

**`-a`, `--all-threads`**

Выгрузить каждый поток в целевом процессе. Без этого флага показывается только главный поток.

**`--native`**

Включать синтетические фреймы `<native>`, отмечающие переходы в C-расширения или другой не-Python код.

**`--no-gc`**

Скрывать синтетические фреймы `<GC>`, которые отмечают активную сборку мусора.

**`--opcodes`**

Аннотировать каждый фрейм опкодом байт-кода, который в данный момент выполняет поток (например, `opcode=CALL_KW`). Полезно для исследования на уровне инструкций, включая определение специализаций, выбранных адаптивным интерпретатором.

**`--async-aware`**

Восстанавливать стеки через границы `await`. `dump` обходит граф задач и выводит по одному разделу на задачу, с маркерами `<task>`, разделяющими корутины, ожидающие друг друга.

**`--async-mode {running,all}`**

Управляет тем, какие задачи включаются, когда `--async-aware` включён. `running` показывает только задачу, выполняющуюся в данный момент в каждом потоке; `all` (по умолчанию для `dump`) также включает задачи, приостановленные в ожидании. У `attach` значение по умолчанию для этого флага – `running`; `dump` по умолчанию `all`, потому что один снимок наиболее полезен, когда показывает полный граф задач.

**`--blocking`**

Приостанавливать каждый поток в цели во время чтения его стека и возобновлять их после. Гарантирует полностью согласованный снимок ценой кратковременной остановки цели. Без этого `dump` читает память, пока цель продолжает работу, что быстрее, но иногда может привести к разорванному стеку.

### Команда `replay`

Команда `replay` преобразует бинарные файлы профилей в другие форматы вывода:

```sh
python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
```

Эта команда полезна, когда собранные данные профилирования в бинарном формате требуется проанализировать позже или преобразовать в формат для визуализации. Бинарные профили можно многократно преобразовывать в разные форматы без повторного профилирования.

```sh
# Преобразовать двоичный файл в pstats (по умолчанию, вывод в stdout)
python -m profiling.sampling replay profile.bin

# Преобразовать двоичный файл в граф пламени
python -m profiling.sampling replay --flamegraph -o output.html profile.bin

# Преобразовать двоичный файл в формат gecko для Firefox Profiler
python -m profiling.sampling replay --gecko -o profile.json profile.bin

# Преобразовать двоичный файл в тепловую карту
python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin
```

### Профилирование в рабочей среде

Семплирующий профилировщик предназначен для использования в рабочей среде. Он не создаёт измеримой нагрузки на целевой процесс, поскольку читает память извне, а не инструментирует код. Целевое приложение продолжает работать на полной скорости и не знает, что его профилируют.

При профилировании рабочих систем следует помнить следующие рекомендации:

Начинайте с коротких интервалов (10–30 секунд), чтобы получить быстрые результаты, а затем увеличивайте, если требуется большая статистическая точность. По умолчанию профилирование выполняется до завершения целевого процесса, чего обычно достаточно для выявления основных «горячих точек».

По возможности профилируйте при типичной нагрузке, а не при пиковом трафике. Профили, собранные при нормальной работе, легче интерпретировать, чем собранные во время необычных всплесков.

Профилировщик сам потребляет ресурсы CPU на машине, где он запущен (не на целевом процессе). На той же машине это обычно незначительно. При профилировании удалённых процессов сетевая задержка не влияет на целевой процесс.

Результаты на продакшне могут отличаться от результатов на разработке из-за разного размера данных, конкурентной нагрузки или эффектов кэширования. Это ожидаемо и зачастую именно то, что нужно зафиксировать.

### Требования к платформе

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

**Linux**

В Linux профилировщик использует `ptrace` или `process_vm_readv` для чтения памяти целевого процесса. Обычно для этого требуется одно из:

- Запуск от root
- Наличие возможности `CAP_SYS_PTRACE`
- Настройка области ptrace Yama: `/proc/sys/kernel/yama/ptrace_scope`

Значение ptrace\_scope по умолчанию (1) ограничивает ptrace только родительскими процессами. Чтобы разрешить прикрепление к любому процессу того же пользователя, установите его в 0:

```sh
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
```

**macOS**

В macOS профилировщик использует `task_for_pid()` для доступа к целевому процессу. Для этого требуется одно из:

- Запуск от root
- Наличие у бинарного файла профилировщика права `com.apple.security.cs.debugger`
- Отключение System Integrity Protection (SIP) (не рекомендуется)

**Windows**

В Windows профилировщику требуются права администратора или привилегия `SeDebugPrivilege` для чтения памяти другого процесса.

### Совместимость версий

Профилировщик и целевой процесс должны работать с одной и той же минорной версией Python (например, обе Python 3.15). Прикрепление из Python 3.14 к процессу Python 3.15 не поддерживается.

Дополнительные ограничения действуют для предварительных версий Python: если профилировщик или цель запущены на предварительной версии (альфа, бета или кандидат в релиз), обе должны использовать точно ту же версию.

В сборках Python со свободной многопоточностью (free-threaded) профилировщик не может прикрепиться из сборки с свободной многопоточностью к стандартной сборке и наоборот.

## Конфигурация сэмплирования

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

Конфигурация по умолчанию хорошо подходит для большинства случаев использования:

| Параметр | По умолчанию |
| --- | --- |
| Значение по умолчанию для `--sampling-rate` / `-r` | 1 кГц |
| Значение по умолчанию для `--duration` / `-d` | Выполнение до завершения |
| Значение по умолчанию для `--all-threads` / `-a` | Только главный поток |
| По умолчанию для `--native` | Нет фреймов `<native>` (время C-кода относится к вызывающему) |
| По умолчанию для `--no-gc` | Фреймы `<GC>` включаются, когда активна сборка мусора |
| По умолчанию для `--mode` | Режим реального времени (все семплы записываются) |
| По умолчанию для `--realtime-stats` | Отключено |
| По умолчанию для `--subprocesses` | Отключено |
| По умолчанию для `--blocking` | Отключено (неблокирующий сбор семплов) |

### Частота и длительность семплирования

Два самых основных параметра – это частота и длительность семплирования. Вместе они определяют, сколько семплов будет собрано за сеанс профилирования.

Параметр [`--sampling-rate`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-r) ([`-r`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-r)) задаёт, как часто собираются семплы. По умолчанию – 1 кГц (1000 семплов в секунду):

```sh
python -m profiling.sampling run -r 20khz script.py
```

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

Параметр [`--duration`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-d) ([`-d`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-d)) задаёт длительность профилирования в секундах. По умолчанию профилирование продолжается, пока целевой процесс не завершится или не будет прерван:

```sh
python -m profiling.sampling run -d 60 script.py
```

Указание длительности полезно при подключении к долго работающим процессам или когда нужно ограничить профилирование определённым временным окном. При профилировании скрипта обычно достаточно поведения по умолчанию – работы до завершения.

### Выбор потоков

Программы на Python часто используют несколько потоков – явно через модуль [`threading`](https://python-all.ru/3.15/library/threading.html#module-threading) или неявно через библиотеки, управляющие пулами потоков.

По умолчанию профилировщик собирает семплы только главного потока. Параметр [`--all-threads`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-a) ([`-a`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-a)) включает сбор семплов всех потоков в процессе:

```sh
python -m profiling.sampling run -a script.py
```

Многопоточное профилирование показывает, как работа распределяется между потоками, и позволяет выявить заблокированные или голодающие потоки. Семплы каждого потока объединяются в выводе, с возможностью фильтрации по потоку в некоторых форматах. Эта возможность особенно полезна при исследовании проблем параллелизма или когда работа распределяется по пулу потоков.

### Блокирующий режим

По умолчанию Tachyon читает память целевого процесса, не останавливая его. Такой неблокирующий подход идеален для большинства сценариев профилирования, поскольку практически не создаёт нагрузки на целевое приложение: профилируемая программа работает на полной скорости и не подозревает, что за ней наблюдают.

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

Для таких случаев параметр [`--blocking`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-blocking) останавливает целевой процесс во время каждого семпла:

```sh
python -m profiling.sampling run --blocking script.py
python -m profiling.sampling attach --blocking 12345
```

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

> **Предупреждение**
>
> Не используйте очень высокие частоты семплирования (маленькие значения `--interval`) с блокирующим режимом. Приостановка и возобновление процесса занимает время, и если интервал семплирования слишком короткий, целевой процесс будет проводить больше времени в остановленном состоянии, чем в работающем. Для блокирующего режима рекомендуются интервалы от 1000 микросекунд (1 миллисекунда) и выше. Интервал по умолчанию в 100 микросекунд может вызвать заметное замедление целевого приложения.

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

### Специальные фреймы

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

Параметр [`--native`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-native) добавляет фреймы `<native>`, указывающие, когда Python вызвал C-код (модули расширений, встроенные функции или сам интерпретатор):

```sh
python -m profiling.sampling run --native script.py
```

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

По умолчанию профилировщик включает фреймы `<GC>`, когда активна сборка мусора. Параметр [`--no-gc`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-no-gc) подавляет эти фреймы:

```sh
python -m profiling.sampling run --no-gc script.py
```

Фреймы GC помогают выявить программы, где сборка мусора отнимает значительное время, что может указывать на шаблоны распределения памяти, которые стоит оптимизировать. Если вы видите значительное время во фреймах `<GC>`, стоит изучить частоту создания объектов или использовать пул объектов.

### Профилирование с учётом опкодов

Опция [`--opcodes`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-opcodes) включает профилирование на уровне инструкций, которое фиксирует, какие инструкции байт-кода Python выполняются на каждом сэмпле:

```sh
python -m profiling.sampling run --opcodes --flamegraph script.py
```

Эта возможность обеспечивает видимость выполнения байт-кода Python, включая оптимизации адаптивной специализации. Когда общая инструкция вроде `LOAD_ATTR` во время выполнения специализируется в более эффективный вариант вроде `LOAD_ATTR_INSTANCE_VALUE`, профилировщик показывает как специализированное имя, так и базовую инструкцию.

Информация об опкодах отображается в нескольких форматах вывода:

- **Графики пламени**: при наведении на кадр отображается всплывающая подсказка с разбивкой инструкций байт-кода, показывающая, какие опкоды заняли время в этой функции.
- **Тепловая карта**: раскрываемые панели байт-кода для каждой строки исходного кода показывают разбивку инструкций с процентами специализации.
- **Режим реального времени**: панель опкодов показывает статистику на уровне инструкций для выбранной функции, доступную через навигацию с клавиатуры.
- **Формат Gecko**: переходы опкодов выводятся в виде маркеров интервалов на временной шкале профилировщика Firefox.

Такой уровень детализации особенно полезен для:

- понимания влияния на производительность адаптивной специализации Python
- выявления горячих инструкций байт-кода, которые могут выиграть от оптимизации
- анализа эффективности различных шаблонов кода на уровне инструкций
- отладки проблем производительности, возникающих на уровне байт-кода

Опция [`--opcodes`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-opcodes) совместима с форматами [`--live`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-live), [`--flamegraph`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-flamegraph), [`--heatmap`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-heatmap) и [`--gecko`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-gecko). Она требует дополнительной памяти для хранения информации об опкодах и может немного снизить производительность сэмплирования, но обеспечивает беспрецедентную видимость модели выполнения Python.

### Статистика в реальном времени

Опция [`--realtime-stats`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-realtime-stats) отображает статистику частоты сэмплирования во время профилирования:

```sh
python -m profiling.sampling run --realtime-stats script.py
```

Это показывает фактическую достигнутую частоту сэмплирования, которая может быть ниже запрошенной, если профилировщик не успевает. Статистика помогает убедиться, что профилирование работает корректно и собирается достаточное количество сэмплов. См. [Эффективность сэмплирования](https://python-all.ru/3.15/library/profiling.sampling.html#sampling-efficiency) для подробностей об интерпретации этих метрик.

### Профилирование подпроцессов

Опция [`--subprocesses`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-subprocesses) включает автоматическое профилирование подпроцессов, порождённых целью:

```sh
python -m profiling.sampling run --subprocesses script.py
python -m profiling.sampling attach --subprocesses 12345
```

При включении профилировщик отслеживает создание дочерних процессов у целевого процесса. Когда обнаружен новый дочерний процесс Python, автоматически запускается отдельный экземпляр профилировщика для его профилирования. Это полезно для приложений, использующих [`multiprocessing`](https://python-all.ru/3.15/library/multiprocessing.html#module-multiprocessing), [`subprocess`](https://python-all.ru/3.15/library/subprocess.html#module-subprocess), [`concurrent.futures`](https://python-all.ru/3.15/library/concurrent.futures.html#module-concurrent.futures) с [`ProcessPoolExecutor`](https://python-all.ru/3.15/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor) или другие механизмы порождения процессов.

worker\_pool.py

```python
from concurrent.futures import ProcessPoolExecutor
import math

def compute_factorial(n):
    total = 0
    for i in range(50):
        total += math.factorial(n)
    return total

if __name__ == "__main__":
    numbers = [5000 + i * 100 for i in range(50)]
    with ProcessPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(compute_factorial, numbers))
    print(f"Computed {len(results)} factorials")
```

```sh
python -m profiling.sampling run --subprocesses --flamegraph worker_pool.py
```

Это создаёт отдельные графики пламени для основного процесса и каждого рабочего процесса: `flamegraph_<main_pid>.html`, `flamegraph_<worker1_pid>.html` и так далее.

Каждый подпроцесс получает свой собственный выходной файл. Имя файла образуется из указанного пути вывода (или пути по умолчанию) с добавлением идентификатора процесса подпроцесса:

- Если указать `-o profile.html`, подпроцессы создадут `profile_12345.html`, `profile_12346.html` и так далее.
- При выводе по умолчанию подпроцессы создают файлы вроде `flamegraph_12345.html` или каталоги вроде `heatmap_12345`.
- Для формата pstats (по умолчанию выводящего в stdout) подпроцессы создают файлы вроде `profile_12345.pstats`.

Профилировщики подпроцессов наследуют большинство параметров сэмплирования от родительского (частоту сэмплирования, длительность, выбор потоков, нативные кадры, кадры GC, режим учёта асинхронности и формат вывода). Все дочерние процессы Python профилируются рекурсивно, включая внуков и дальнейших потомков.

Обнаружение подпроцессов работает путём периодического сканирования новых потомков целевого процесса и проверки, является ли каждый новый процесс процессом Python, путём зондирования памяти процесса на наличие структур времени выполнения Python. Непроцессы Python (например, команды оболочки или внешние инструменты) игнорируются.

Существует ограничение в 100 одновременных профилировщиков подпроцессов, чтобы предотвратить истощение ресурсов в программах, порождающих много процессов. Если этот лимит достигнут, дополнительные подпроцессы не профилируются, и выводится предупреждение.

Опция [`--subprocesses`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-subprocesses) несовместима с режимом [`--live`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-live), поскольку режим реального времени использует интерактивный терминальный интерфейс, который не может поддерживать несколько одновременных отображений профилировщика.

### Эффективность сэмплирования

Метрики эффективности сэмплирования помогают оценить качество собранных данных. Эти метрики отображаются в терминальном выводе профилировщика и на боковой панели графика пламени.

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

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

Обе метрики носят информационный характер. Даже при наличии неудачных попыток или пропущенных сэмплов профиль остаётся статистически валидным, пока собрано достаточно сэмплов. Профилировщик сообщает фактическое количество захваченных сэмплов, которое можно использовать, чтобы оценить, достаточно ли данных для анализа.

## Режимы профилирования

Сэмплирующий профилировщик поддерживает четыре режима, которые определяют, какие сэмплы записываются. Режим задаёт, что измеряет профиль: общее затраченное время, время выполнения на CPU, время удержания глобальной блокировки интерпретатора или обработку исключений.

### Режим календарного времени

Режим календарного времени ([`--mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-mode)`=wall`) захватывает все сэмплы независимо от того, чем занят поток. Это режим по умолчанию, который даёт полную картину того, где тратится время при выполнении программы:

```sh
python -m profiling.sampling run --mode=wall script.py
```

В режиме календарного времени сэмплы записываются независимо от того, активно ли поток выполняет код Python, ожидает ввода-вывода, заблокирован на блокировке или спит. Это делает профилирование в режиме календарного времени идеальным для понимания общего распределения времени в программе, включая время ожидания.

Если программа тратит значительное время на операции ввода-вывода, сетевые вызовы или сон, режим календарного времени покажет это ожидание как время, приписанное вызывающей функции. Часто это именно то, что нужно при оптимизации сквозной задержки.

### Режим CPU

Режим CPU ([`--mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-mode)`=cpu`) записывает сэмплы только тогда, когда поток фактически выполняется на ядре CPU:

```sh
python -m profiling.sampling run --mode=cpu script.py
```

Сэмплы, взятые во время сна потока, блокировки на I/O или ожидания блокировки, отбрасываются. Полученный профиль показывает, где расходуются циклы CPU, отфильтровывая время простоя.

Режим CPU полезен, когда нужно сосредоточиться на вычислительных горячих точках, не отвлекаясь на ожидания I/O. Если программа чередует вычисления и сетевые вызовы, режим CPU показывает, какие вычислительные участки наиболее затратны.

### Сравнение профилей календарного времени и CPU

Запуск профилей в режиме календарного времени и CPU может показать, тратит ли функция время на вычисления или ожидание.

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

Если функция высока в режиме календарного времени, но низка или отсутствует в режиме CPU, она привязана к I/O или ожиданию. Функция тратит большую часть времени на ожидание сети, диска, блокировок или сна. Оптимизация CPU здесь не поможет; вместо этого рассмотрите асинхронный I/O, пул соединений или сокращение времени ожидания.

```python
import time

def do_sleep():
    time.sleep(2)

def do_compute():
    sum(i**2 for i in range(1000000))

if __name__ == "__main__":
    do_sleep()
    do_compute()
```

```sh
python -m profiling.sampling run --mode=wall script.py  # do_sleep ~98%, do_compute ~1%
python -m profiling.sampling run --mode=cpu script.py   # do_sleep отсутствует, преобладает do_compute
```

### Режим GIL

Режим GIL ([`--mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-mode)`=gil`) записывает сэмплы только тогда, когда поток удерживает глобальную блокировку интерпретатора Python:

```sh
python -m profiling.sampling run --mode=gil script.py
```

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

В многопоточных программах режим GIL показывает, какой код мешает другим потокам выполнять байт-код Python. Поскольку только один поток может одновременно удерживать GIL, функции, которые часто появляются в профилях режима GIL, монополизируют интерпретатор.

Режим GIL помогает ответить на вопросы вроде «какие функции монополизируют GIL?» и «почему мои другие потоки голодают?». Он также может быть полезен в однопоточных программах, чтобы отличить время выполнения Python от времени, затраченного в расширениях C или I/O.

```python
import hashlib

def hash_work():
    # Расширение на C – освобождает GIL во время вычислений
    for _ in range(200):
        hashlib.sha256(b"data" * 250000).hexdigest()

def python_work():
    # Чистый Python – удерживает GIL во время вычислений
    for _ in range(3):
        sum(i**2 for i in range(1000000))

if __name__ == "__main__":
    hash_work()
    python_work()
```

```sh
python -m profiling.sampling run --mode=cpu script.py  # hash_work ~42%, python_work ~38%
python -m profiling.sampling run --mode=gil script.py  # hash_work ~5%, python_work ~60%
```

### Режим исключений

Режим исключений (`--mode=exception`) записывает сэмплы только тогда, когда у потока активное исключение:

```sh
python -m profiling.sampling run --mode=exception script.py
```

Сэмплы записываются в двух ситуациях: когда исключение распространяется вверх по стеку вызовов (после `raise`, но до перехвата), или когда код выполняется внутри блока `except`, где информация об исключении всё ещё присутствует в состоянии потока.

Следующий пример иллюстрирует, какие области кода захватываются:

```python
def example():
    try:
        raise ValueError("error")    # Захвачено: возбуждается исключение
    except ValueError:
        process_error()              # Захвачено: внутри блока except
    finally:
        cleanup()                    # НЕ захвачено: исключение уже обработано

def example_propagating():
    try:
        try:
            raise ValueError("error")
        finally:
            cleanup()                # Захвачено: исключение распространяется через
    except ValueError:
        pass

def example_no_exception():
    try:
        do_work()
    finally:
        cleanup()                    # НЕ захвачено: исключение не задействовано
```

Обратите внимание, что блоки `finally` захватываются только тогда, когда через них активно распространяется исключение. Как только блок `except` заканчивает выполнение, Python очищает информацию об исключении перед запуском любого последующего блока `finally`. Аналогично, блоки `finally`, которые выполняются при нормальном ходе (когда исключение не было возбуждено), не захватываются, поскольку состояние исключения отсутствует.

Этот режим полезен для понимания того, где программа тратит время на обработку ошибок. Обработка исключений может быть значительным источником накладных расходов в коде, который использует исключения для управления потоком (например, `StopIteration` в итераторах) или в приложениях, обрабатывающих множество ошибочных ситуаций (например, сетевые серверы, обрабатывающие сбои соединений).

Режим исключений помогает ответить на вопросы вроде «сколько времени тратится на обработку исключений?» и «какие обработчики исключений самые дорогие?». Он может выявить скрытые затраты производительности в коде, который перехватывает и обрабатывает множество исключений, даже если эти исключения обрабатываются корректно. Например, если библиотека синтаксического анализа внутренне использует исключения для сигнализации об ошибках формата, этот режим зафиксирует время, затраченное в этих обработчиках, даже если вызывающий код никогда не видит исключений.

## Форматы вывода

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

### Формат pstats

Формат pstats ([`--pstats`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-pstats)) создаёт текстовую таблицу, аналогичную той, что генерируют детерминированные профилировщики. Это формат вывода по умолчанию:

```sh
python -m profiling.sampling run script.py
python -m profiling.sampling run --pstats script.py
```

[![Терминальный вывод Tachyon pstats](https://python-all.ru/3.15/_images/tachyon-pstats.png)](https://python-all.ru/3.15/_images/tachyon-pstats.png)

Формат pstats отображает результаты профилирования в таблице с цветовой кодировкой, показывающей горячие точки функций, количество сэмплов и временные оценки.

Вывод по умолчанию отображается в stdout:

```sh
Profile Stats (Mode: wall):
     nsamples  sample%    tottime (ms)  cumul%   cumtime (ms)  filename:lineno(function)
       234/892    11.7%       234.00     44.6%       892.00    server.py:145(handle_request)
       156/156     7.8%       156.00      7.8%       156.00    <built-in>:0(socket.recv)
        98/421     4.9%        98.00     21.1%       421.00    parser.py:67(parse_message)
```

Столбцы показывают количество сэмплов и примерное время:

- **nsamples**: Отображается как `direct/cumulative` (например, `10/50`). Прямые сэмплы – это когда функция находилась на вершине стека и активно выполнялась. Накопительные сэмплы – когда функция появлялась где-либо в стеке, включая ожидание вызванных ею функций. Если функция показывает `10/50`, она напрямую выполнялась в 10 сэмплах и всего была в стеке вызовов в 50 сэмплах.
- **sample%** и **cumul%**: Проценты от общего количества сэмплов для прямых и накопительных значений соответственно.
- **tottime** и **cumtime**: Примерное астрономическое время на основе количества сэмплов и длительности профилирования. Единицы времени выбираются автоматически в зависимости от величины: секунды для больших значений, миллисекунды для средних или микросекунды для малых.

Вывод содержит легенду, объясняющую каждый столбец, и сводку интересных функций, которая выделяет:

- **Горячие точки**: Функции с высоким отношением прямых сэмплов к накопительным (отношение близко к 1,0). Эти функции тратят большую часть времени на выполнение собственного кода, а не на ожидание вызываемых функций. Высокие отношения указывают, где на самом деле расходуется процессорное время.
- **Косвенные вызовы**: Функции с большой разницей между накопительными и прямыми сэмплами. Это функции-оркестраторы, которые делегируют работу другим функциям. Они часто появляются в стеке, но редко на вершине.
- **Увеличение вызовов**: Функции, где накопительные сэмплы значительно превышают прямые (высокий множитель накопительных к прямым). Это часто вложенные функции, появляющиеся глубоко во многих цепочках вызовов.

Используйте [`--no-summary`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-no-summary), чтобы скрыть как легенду, так и сводку.

Чтобы сохранить вывод pstats в двоичный файл вместо stdout:

```sh
python -m profiling.sampling run -o profile.pstats script.py
```

Формат pstats поддерживает несколько опций для управления отображением. Опция [`--sort`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-sort) определяет столбец, используемый для упорядочивания результатов:

```sh
python -m profiling.sampling run --sort=tottime script.py
python -m profiling.sampling run --sort=cumtime script.py
python -m profiling.sampling run --sort=nsamples script.py
```

Опция [`--limit`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-l) ограничивает вывод первыми N записями:

```sh
python -m profiling.sampling run --limit=30 script.py
```

Опция [`--no-summary`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-no-summary) скрывает заголовок сводки, который предшествует таблице статистики.

### Формат свернутых стеков

Формат свернутых стеков ([`--collapsed`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-collapsed)) создаёт одну строку для каждого уникального стека вызовов с указанием количества сэмплов этого стека:

```sh
python -m profiling.sampling run --collapsed script.py
```

Вывод выглядит так:

```text
main;process_data;parse_json;decode_utf8 42
main;process_data;parse_json 156
main;handle_request;send_response 89
```

Каждая строка содержит названия функций, разделённые точкой с запятой, представляющие стек вызовов снизу вверх, затем пробел и количество сэмплов. Этот формат предназначен для совместимости с внешними инструментами построения флейм-графов, в частности со скриптом `flamegraph.pl` Брендана Грегга.

Чтобы построить флейм-граф на основе свернутых стеков:

```sh
python -m profiling.sampling run --collapsed script.py > stacks.txt
flamegraph.pl stacks.txt > profile.svg
```

Полученный SVG можно открыть в любом веб-браузере; он предоставляет интерактивную визуализацию, где можно щёлкать для увеличения отдельных путей вызовов.

### Формат флейм-графа

Формат флейм-графа ([`--flamegraph`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-flamegraph)) создаёт самодостаточный HTML-файл с интерактивной визуализацией флейм-графа:

```sh
python -m profiling.sampling run --flamegraph script.py
python -m profiling.sampling run --flamegraph -o profile.html script.py
```

[![Интерактивный флейм-граф Tachyon](https://python-all.ru/3.15/_images/tachyon-flamegraph.png)](https://python-all.ru/3.15/_images/tachyon-flamegraph.png)

Визуализация флейм-графа отображает стеки вызовов в виде вложенных прямоугольников, ширина которых пропорциональна затраченному времени. На боковой панели отображается статистика выполнения, метрики GIL и горячие точки функций.

[Попробуйте интерактивный пример](https://python-all.ru/3.15/_static/tachyon-example-flamegraph.html)!

Если не указан выходной файл, профилировщик генерирует имя файла на основе идентификатора процесса (например, `flamegraph.12345.html`).

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

Интерфейс флейм-графа включает:

- Боковую панель, показывающую сводку профиля, статистику потоков, метрики эффективности сэмплирования (см. [Эффективность сэмплирования](https://python-all.ru/3.15/library/profiling.sampling.html#sampling-efficiency)) и основные горячие точки функций
- Функцию поиска, поддерживающую как сопоставление имён функций, так и шаблоны `file.py:42` строк
- Фильтрацию по потокам через выпадающее меню
- Переключатель тёмной/светлой темы (настройка сохраняется между сеансами)
- Экспорт в SVG для сохранения текущего вида

В разделе статистики потоков отображаются метрики поведения во время выполнения:

- **GIL Held**: процент выборок, в которых поток удерживал глобальную блокировку интерпретатора (активно выполнял код Python)
- **GIL Released**: процент выборок, в которых ни один поток не удерживал GIL
- **Waiting GIL**: процент выборок, в которых поток ожидал захвата GIL
- **GC**: процент выборок во время сборки мусора

Эти статистики помогают выявить конкуренцию за GIL и понять, как время распределяется между выполнением Python, нативным кодом и ожиданием.

Флейм-графы особенно эффективны для выявления глубоких стеков вызовов и понимания иерархической структуры потребления времени. Широкие прямоугольники вверху указывают на функции, которые потребляют значительное время либо напрямую, либо через вызываемые ими функции.

#### Дифференциальные флейм-графы

Дифференциальные флейм-графы сравнивают два профилировочных запуска, чтобы выделить места, где изменилась производительность. Это помогает выявить регрессии, внесённые изменениями кода, и проверить, что оптимизации достигли желаемого эффекта:

```sh
# Захватить базовый профиль
python -m profiling.sampling run --binary -o baseline.bin script.py

# После изменения кода сгенерировать дифференциальный граф пламени
python -m profiling.sampling run --diff-flamegraph baseline.bin -o diff.html script.py
```

Визуализация отображает текущий профиль: ширина фреймов показывает текущее потребление времени, а цвет указывает на то, как изменилась каждая функция относительно базового профиля.

**Цветовая кодировка**:

- **Красный**: функции, потребляющие больше времени (регрессии). Светлые оттенки указывают на незначительное увеличение, тёмные – на серьёзные регрессии.
- **Синий**: функции, потребляющие меньше времени (улучшения). Светлые оттенки – для незначительных уменьшений, тёмные – для значительных ускорений.
- **Серый**: минимальные или нулевые изменения.
- **Фиолетовый**: новые функции, отсутствовавшие в базовом профиле.

Цвета фреймов показывают изменения в **прямом времени** (время, когда функция находилась на вершине стека и активно выполнялась), а не в кумулятивном времени, включающем вызываемые функции. При наведении на фрейм отображаются детали сравнения: время в базовом профиле, текущее время и процент изменения.

Некоторые пути вызовов могут полностью исчезнуть между профилями. Они называются **элидированными стеками** и возникают, когда оптимизации устраняют пути кода или некоторые ветки перестают выполняться. Если есть элидированные стеки, появляется переключатель, позволяющий переключаться между основным дифференциальным представлением и представлением только элидированных путей, показывающим удалённые пути (окрашены фиолетовым).

### Формат Gecko

Формат Gecko ([`--gecko`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-gecko)) создаёт JSON-вывод, совместимый с Firefox Profiler:

```sh
python -m profiling.sampling run --gecko script.py
python -m profiling.sampling run --gecko -o profile.json script.py
```

[Firefox Profiler](https://python-all.ru/3.15/library/profiling.sampling.html) – это сложный веб-инструмент, изначально созданный для профилирования самого Firefox. Он предоставляет возможности, выходящие за рамки базовых флейм-графов, включая временную шкалу, исследование дерева вызовов и визуализацию маркеров. Инструкции по использованию см. в [документации Firefox Profiler](https://python-all.ru/3.15/library/profiling.sampling.html).

Чтобы использовать результат, откройте Firefox Profiler в браузере и загрузите JSON-файл. Профилировщик работает полностью на стороне клиента, поэтому ваши данные профилирования никогда не покидают вашу машину.

Формат Gecko автоматически собирает дополнительные метаданные о состоянии GIL и активности ЦП, что позволяет использовать функции анализа, специфичные для модели потоков Python. Профилировщик испускает интервальные маркеры, которые отображаются в виде цветных полос на временной шкале Firefox Profiler:

- **Маркеры GIL**: показывают, когда потоки удерживают или освобождают GIL
- **Маркеры ЦП**: показывают, когда потоки выполняются на ЦП, а когда простаивают
- **Маркеры типа кода**: различают код Python и нативный код (расширения C)
- **Маркеры GC**: указывают на активность сборщика мусора

По этой причине опция [`--mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-mode) недоступна в формате Gecko; все соответствующие данные собираются автоматически.

[![Представление дерева вызовов профилировщика Firefox](https://python-all.ru/3.15/_images/tachyon-gecko-calltree.png)](https://python-all.ru/3.15/_images/tachyon-gecko-calltree.png)

В представлении «Дерево вызовов» отображается полная иерархия вызовов с количеством сэмплов и процентами. На боковой панели показываются детальные статистики для выбранной функции, включая время выполнения и распределение сэмплов.

[![Представление флейм-графа профилировщика Firefox](https://python-all.ru/3.15/_images/tachyon-gecko-flamegraph.png)](https://python-all.ru/3.15/_images/tachyon-gecko-flamegraph.png)

Визуализация флейм-графа показывает стеки вызовов в виде вложенных прямоугольников. Имена функций видны в иерархии вызовов.

[![Диаграмма маркеров профилировщика Firefox с кодами операций](https://python-all.ru/3.15/_images/tachyon-gecko-opcodes.png)](https://python-all.ru/3.15/_images/tachyon-gecko-opcodes.png)

Диаграмма маркеров отображает интервальные маркеры, включая состояние ЦП, статус GIL и коды операций. При включённом `--opcodes` инструкции байткода, такие как `BINARY_OP_ADD_FLOAT`, `CALL_PY_EXACT_ARGS` и `CALL_LIST_APPEND`, появляются в качестве маркеров, показывающих выполнение во времени.

### Формат тепловой карты

Формат тепловой карты ([`--heatmap`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-heatmap)) создаёт интерактивную HTML-визуализацию, показывающую количество сэмплов на уровне строк исходного кода:

```sh
python -m profiling.sampling run --heatmap script.py
python -m profiling.sampling run --heatmap -o my_heatmap script.py
```

[![Визуализация тепловой карты Tachyon](https://python-all.ru/3.15/_images/tachyon-heatmap.png)](https://python-all.ru/3.15/_images/tachyon-heatmap.png)

Тепловая карта накладывает количество сэмплов прямо на исходный код. Строки окрашиваются от холодных (мало сэмплов) до горячих (много сэмплов). Кнопки навигации (▲▼) позволяют переходить между вызывающими и вызываемыми функциями.

В отличие от других форматов, которые создают один файл, вывод тепловой карты формирует каталог, содержащий HTML-файлы для каждого профилируемого исходного файла. Если путь вывода не указан, каталог называется `heatmap_PID`.

Визуализация тепловой карты отображает исходный код с цветовым градиентом, показывающим, сколько сэмплов было собрано для каждой строки. Горячие строки (много сэмплов) отображаются тёплыми цветами, а холодные (мало или нет сэмплов) – холодными. Такой вид помогает точно определить, какие строки кода ответственны за затраты времени.

Интерфейс тепловой карты предоставляет несколько интерактивных возможностей:

- **Режимы окраски**: переключение между «Собственное время» (непосредственное выполнение) и «Общее время» (суммарное, включая время в вызываемых функциях)
- **Фильтрация холодного кода**: показывать все строки или только строки с сэмплами
- **Навигация по графу вызовов**: каждая строка показывает кнопки навигации (▲ для вызывающих, ▼ для вызываемых), позволяющие проследить пути выполнения в коде. Когда несколько функций вызывают или вызываются из строки, появляется меню со всеми вариантами и количеством сэмплов.
- **Миникарта прокрутки**: вертикальный обзор, показывающий распределение тепла по всему файлу
- **Иерархический индекс**: файлы, сгруппированные по типу (stdlib, site-packages, project) с суммарным количеством сэмплов в каждой папке
- **Тёмная/светлая тема**: переключение с сохранением предпочтения между сеансами
- **Ссылки на строки**: клик по номеру строки для создания ссылок, которыми можно поделиться

Если включено профилирование на уровне опкодов с помощью [`--opcodes`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-opcodes), каждую горячую строку можно развернуть, чтобы увидеть, какие инструкции байт-кода потребовали времени:

[![Тепловая карта с развёрнутой панелью байт-кода](https://python-all.ru/3.15/_images/tachyon-heatmap-with-opcodes.png)](https://python-all.ru/3.15/_images/tachyon-heatmap-with-opcodes.png)

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

[Попробуйте интерактивный пример](https://python-all.ru/3.15/_static/tachyon-example-heatmap.html)!

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

### Бинарный формат

Бинарный формат ([`--binary`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-binary)) создаёт компактный бинарный файл для эффективного хранения данных профилирования:

```sh
python -m profiling.sampling run --binary -o profile.bin script.py
python -m profiling.sampling attach --binary -o profile.bin 12345
```

Параметр [`--compression`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-compression) управляет сжатием данных:

- `auto` (по умолчанию): использовать сжатие zstd, если доступно, иначе без сжатия
- `zstd`: принудительно использовать сжатие zstd (требуется поддержка [`compression.zstd`](https://python-all.ru/3.15/library/compression.zstd.html#module-compression.zstd))
- `none`: отключить сжатие

```sh
python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py
```

Для анализа бинарных профилей используйте [команду replay](https://python-all.ru/3.15/library/profiling.sampling.html#replay-command), чтобы преобразовать их в другие форматы, например, flame graphs или вывод pstats.

## Рабочий процесс записи и воспроизведения

Бинарный формат в сочетании с командой replay обеспечивает рабочий процесс записи и воспроизведения, который разделяет сбор данных и анализ. Вместо создания визуализаций во время профилирования сырые данные сохраняются в компактный бинарный файл, а затем преобразуются в разные форматы.

У этого подхода три основных преимущества:

- Сэмплирование выполняется быстрее, поскольку построение структур данных для визуализации откладывается до момента воспроизведения.
- Один бинарный файл с данными можно преобразовать в несколько форматов вывода без повторного профилирования: pstats для быстрого обзора, flame graph для визуального исследования, тепловую карту для детализации на уровне строк.
- Бинарные файлы компактны, и ими легко делиться с коллегами, которые могут преобразовать их в предпочитаемый формат.

Типичный рабочий процесс:

```sh
# Захватить профиль в продакшене или во время тестов
python -m profiling.sampling attach --binary -o profile.bin 12345

# Позже проанализировать в различных форматах
python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
python -m profiling.sampling replay --heatmap -o heatmap profile.bin
```

## Режим реального времени

Режим реального времени ([`--live`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-live)) предоставляет терминальный просмотр данных профилирования в реальном времени, аналогично команде `top` для системных процессов:

```sh
python -m profiling.sampling run --live script.py
python -m profiling.sampling attach --live 12345
```

[![Режим реального времени Tachyon, показывающий все потоки](https://python-all.ru/3.15/_images/tachyon-live-mode-2.gif)](https://python-all.ru/3.15/_images/tachyon-live-mode-2.gif)

В режиме реального времени отображается статистика профилирования в реальном времени, объединяющая данные из нескольких потоков в многопоточном приложении.

Дисплей непрерывно обновляется по мере поступления новых выборок, показывая текущие самые «горячие» функции. Этот режим требует модуля [`curses`](https://python-all.ru/3.15/library/curses.html#module-curses), который доступен в Unix-подобных системах, но не в Windows. Терминал должен быть шириной не менее 60 столбцов и высотой не менее 12 строк; в больших терминалах отображается больше столбцов.

В заголовке отображаются три самые «горячие» функции, метрики эффективности выборки и статистика состояния потоков (процент удержания GIL, загрузка ЦП, время GC). Основная таблица показывает статистику функций; текущий столбец сортировки обозначается стрелкой (▼).

Если [`--opcodes`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-opcodes) включён, под основной таблицей появляется дополнительная панель с опкодами, показывающая статистику на уровне инструкций для выбранной функции. Эта панель отображает, какие инструкции байткода выполняются чаще всего, включая специализированные варианты и их базовые опкоды.

[![Живой режим Tachyon с панелью опкодов](https://python-all.ru/3.15/_images/tachyon-live-mode-1.gif)](https://python-all.ru/3.15/_images/tachyon-live-mode-1.gif)

Режим реального времени с включённым `--opcodes` показывает панель опкодов с разбивкой инструкций байткода для выбранной функции.

### Команды клавиатуры

В режиме реального времени команды клавиатуры управляют отображением:

**`q`**

Выйти из профилировщика и вернуться в оболочку.

**`s` / `S`**

Переключение порядка сортировки вперёд/назад (количество выборок, процент, общее время, накопленный процент, накопленное время).

**`p`**

Приостановить или возобновить обновление дисплея. Во время паузы выборка продолжается в фоне, так что можно заморозить просмотр для изучения результатов, не останавливая сбор данных.

**`r`**

Сбросить всю статистику и начать заново. Эта команда отключается после завершения профилирования, чтобы предотвратить случайную потерю данных.

**`/`**

Вход в режим фильтрации для поиска функций по имени. Фильтр использует поиск подстроки без учёта регистра по имени файла и функции. Введите шаблон и нажмите Enter для применения или Escape для отмены. Глоб-шаблоны и регулярные выражения не поддерживаются.

**`c`**

Очистить текущий фильтр и снова показать все функции.

**`t`**

Переключение между просмотром всех потоков вместе или статистикой по каждому потоку. В режиме «по потокам» появляется счётчик потоков (например, `1/4`), показывающий текущую позицию среди доступных потоков.

**`←` `→` или `↑` `↓`**

В режиме просмотра по потокам переключение между потоками. Переход зациклен: от последнего потока к первому и наоборот.

**`+` / `-`**

Увеличить или уменьшить частоту обновления дисплея. Диапазон: от 0,05 секунды (20 Гц, очень отзывчиво) до 1,0 секунды (1 Гц, меньшая нагрузка). Более высокая частота обновления потребляет больше ЦП. По умолчанию – 0,1 секунды (10 Гц).

**`x`**

Включение/выключение индикаторов тренда, показывающих, становятся ли функции «горячее» или «холоднее» со временем. При включении увеличивающиеся метрики отображаются зелёным, а уменьшающиеся – красным; сравнивается каждое обновление с предыдущим.

**`h` или `?`**

Показать экран справки со всеми доступными командами.

**`j` / `k` (или `Up` / `Down`)**

Навигация по записям опкодов на панели опкодов (когда `--opcodes` включён). Эти клавиши прокручивают статистику на уровне инструкций для выбранной функции.

Когда профилирование завершается (истекает длительность или завершается целевой процесс), на дисплее появляется баннер «PROFILING COMPLETE» (ПРОФИЛИРОВАНИЕ ЗАВЕРШЕНО) и фиксируются окончательные результаты. Можно по-прежнему перемещаться, сортировать и фильтровать результаты, прежде чем нажать `q` для выхода.

Режим реального времени несовместим с опциями формата вывода ([`--collapsed`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-collapsed), [`--flamegraph`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-flamegraph) и т. д.), поскольку он использует интерактивный терминальный интерфейс, а не создаёт файловый вывод.

## Профилирование с учётом асинхронности

Для программ, использующих [`asyncio`](https://python-all.ru/3.15/library/asyncio.html#module-asyncio), профилировщик предлагает режим с учётом асинхронности ([`--async-aware`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-async-aware)), который восстанавливает стеки вызовов на основе структуры задач, а не необработанных фреймов Python:

```sh
python -m profiling.sampling run --async-aware async_script.py
```

Стандартное профилирование асинхронного кода может сбивать с толку, поскольку физический стек вызовов часто показывает внутренности цикла событий, а не логический поток корутин. Режим с учётом асинхронности решает эту проблему, отслеживая, какая задача выполняется, и представляя стеки, отражающие цепочку `await`.

```python
import asyncio

async def fetch(url):
    await asyncio.sleep(0.1)
    return url

async def main():
    for _ in range(50):
        await asyncio.gather(fetch("a"), fetch("b"), fetch("c"))

if __name__ == "__main__":
    asyncio.run(main())
```

```sh
python -m profiling.sampling run --async-aware --flamegraph -o out.html script.py
```

> **Примечание**
>
> Для профилирования с учётом асинхронности требуется, чтобы в целевом процессе был загружен модуль [`asyncio`](https://python-all.ru/3.15/library/asyncio.html#module-asyncio). Если вы профилируете скрипт до того, как он импортирует asyncio, режим с учётом асинхронности не сможет захватывать информацию о задачах.

### Режимы асинхронности

Опция [`--async-mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-async-mode) управляет тем, какие задачи отображаются в профиле:

```sh
python -m profiling.sampling run --async-aware --async-mode=running async_script.py
python -m profiling.sampling run --async-aware --async-mode=all async_script.py
```

С [`--async-mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-async-mode)`=running` (по умолчанию) профилируется только задача, выполняющаяся в данный момент на ЦП. Это показывает, где ваша программа активно тратит время, и является типичным выбором для анализа производительности.

С [`--async-mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-async-mode)`=all` также включаются приостановленные задачи (ожидающие ввода-вывода, блокировок или других задач). Этот режим полезен для понимания того, чего ждёт ваша программа, но создаёт более крупные профили, поскольку каждая приостановленная задача появляется в каждой выборке.

### Маркеры задач и восстановление стека

В профилях с поддержкой async вы увидите кадры `<task>`, которые отмечают границы между задачами asyncio. Это синтетические кадры, вставленные профилировщиком для отображения структуры задач. В этих кадрах имя задачи отображается как имя функции.

Когда задача ожидает другую задачу, профилировщик восстанавливает логическую цепочку вызовов, следуя по связям `await`. Только «листовые» задачи (задачи, которые в данный момент не ожидаются другой задачей) создают собственные записи в стеке. Задачи, ожидаемые другими задачами, отображаются как часть стека ожидающей их задачи.

Если у задачи несколько ожидающих (ромбовидный паттерн в графе задач), профилировщик детерминированно выбирает одного родителя и помечает маркер задачи количеством родителей, например `MyTask (2 parents)`. Это указывает на существование альтернативных путей выполнения, которые не показаны в данном конкретном стеке.

### Ограничения параметров

Режим с поддержкой async использует другой механизм восстановления стека и несовместим с: [`--native`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-native), [`--no-gc`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-no-gc), [`--all-threads`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-a), а также [`--mode`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-mode)`=cpu` или `--mode``=gil`.

## Интерфейс командной строки

Полный интерфейс командной строки для справки.

### Глобальные параметры

#### `run`

Запуск и профилирование скрипта или модуля Python.

#### `attach`

Подключение к запущенному процессу по PID и его профилирование.

#### `dump`

Вывод однократного снимка стека Python запущенного процесса.

#### `replay`

Преобразование бинарного файла профиля в другой формат вывода.

### Параметры dump

Следующие параметры применяются к подкоманде `dump`:

#### `-a, --all-threads`

Дамп всех потоков в целевом процессе вместо только главного потока.

#### `--native`

Включать кадры `<native>` для кода не на Python.

#### `--no-gc`

Исключить кадры `<GC>` для активной сборки мусора.

#### `--opcodes`

Показывать имена опкодов байт-кода, если доступны.

#### `--async-aware`

Восстанавливать стек через границы `await` для приложений asyncio.

#### `--async-mode <mode>`

Режим асинхронного стека: `running` (только запущенная задача) или `all` (все задачи, включая ожидающие). По умолчанию `all` для `dump`. Требует [`--async-aware`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-async-aware).

#### `--blocking`

Приостанавливать все потоки в целевом процессе при чтении стека.

### Параметры сэмплирования

#### `-r <rate>, --sampling-rate <rate>`

Частота дискретизации (например, `10000`, `10khz`, `10k`). По умолчанию: `1khz`.

#### `-d <seconds>, --duration <seconds>`

Длительность профилирования в секундах. По умолчанию: до завершения.

#### `-a, --all-threads`

Выполнять семплирование всех потоков, а не только основного.

#### `--realtime-stats`

Отображать статистику семплирования во время профилирования.

#### `--native`

Включать кадры `<native>` для кода не на Python.

#### `--no-gc`

Исключать кадры `<GC>` для сборки мусора.

#### `--async-aware`

Включить профилирование с учётом асинхронности для программ asyncio.

#### `--opcodes`

Собирает информацию об опкодах байткода для профилирования на уровне инструкций. Показывает, какие инструкции байткода выполняются, включая специализации. Совместимо только с форматами `--live`, `--flamegraph`, `--heatmap` и `--gecko`.

#### `--subprocesses`

Также профилировать подпроцессы. Каждый подпроцесс получает собственный экземпляр профилировщика и выходной файл. Несовместимо с `--live`.

#### `--blocking`

Приостанавливать целевой процесс при каждом семпле. Это обеспечивает согласованные стек-трейсы ценой замедления цели. Используйте с большими интервалами (1000 мкс и выше), чтобы минимизировать влияние. См. [Режим блокировки](https://python-all.ru/3.15/library/profiling.sampling.html#blocking-mode) подробнее.

### Параметры режима

#### `--mode <mode>`

Режим семплирования: `wall` (по умолчанию), `cpu`, `gil` или `exception`. Режимы `cpu`, `gil` и `exception` несовместимы с `--async-aware`.

#### `--async-mode <mode>`

Режим асинхронного профилирования: `running` (по умолчанию) или `all`. Требуется `--async-aware`.

### Параметры вывода

#### `--pstats`

Генерировать статистику pstats. Это значение по умолчанию. При выводе в stdout результат – текстовая таблица; с [`-o`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-o) это бинарный файл pstats.

#### `--collapsed`

Генерировать свернутый формат стека для внешних инструментов построения пламенных графов.

#### `--flamegraph`

Генерировать самодостаточный HTML-пламенный граф.

#### `--diff-flamegraph <baseline.bin>`

Создать дифференциальную флейм-графию для сравнения с базовым бинарным профилем.

#### `--gecko`

Создать вывод в формате Gecko JSON для Firefox Profiler.

#### `--heatmap`

Создать HTML-тепловую карту с количеством сэмплов по строкам.

#### `--binary`

Создать высокопроизводительный бинарный формат для последующего преобразования с помощью команды `replay`.

#### `--compression <type>`

Сжатие для бинарного формата: `auto` (использовать zstd, если доступен, по умолчанию), `zstd` или `none`.

#### `-o <path>, --output <path>`

Путь к выходному файлу или каталогу. Поведение по умолчанию зависит от формата: [`--pstats`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-pstats) выводит текстовую таблицу в stdout, а `-o` записывает бинарный файл pstats. Другие форматы создают файл с именем `<format>_<PID>.<ext>` (например, `flamegraph_12345.html`). [`--heatmap`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-heatmap) создает каталог с именем `heatmap_<PID>`.

#### `--browser`

Автоматически открывать HTML-вывод ([`--flamegraph`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-flamegraph) и [`--heatmap`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-heatmap)) в веб-браузере по умолчанию после создания. При профилировании с помощью [`--subprocesses`](https://python-all.ru/3.15/library/profiling.sampling.html#cmdoption-profiling.sampling-subprocesses) браузер открывается только в главном процессе; выводы подпроцессов никогда не открываются автоматически.

### Параметры отображения pstats

Эти параметры применяются только к выводу в формате pstats.

#### `--sort <key>`

Порядок сортировки: `nsamples`, `tottime`, `cumtime`, `sample-pct`, `cumul-pct`, `nsamples-cumul` или `name`. По умолчанию: `nsamples`.

#### `-l <count>, --limit <count>`

Максимальное количество записей для отображения. По умолчанию: 15.

#### `--no-summary`

Исключить из вывода разделы «Легенда» и «Сводка интересующих функций».

### Параметры команды запуска

#### `-m, --module`

Рассматривать целевой объект как имя модуля, а не как путь к скрипту.

#### `--live`

Запустить интерактивный терминальный интерфейс вместо пакетного профилирования.

> **См. также**
>
> **[`profiling`](https://python-all.ru/3.15/library/profiling.html#module-profiling)**
>
> Обзор инструментов профилирования Python и рекомендации по выбору профилировщика.
>
> **[`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing)**
>
> Детерминированный трассирующий профилировщик для точного подсчета вызовов и измерения времени.
>
> **[`pstats`](https://python-all.ru/3.15/library/pstats.html#module-pstats)**
>
> Статистический анализ данных профиля.
>
> **[Firefox Profiler](https://python-all.ru/3.15/library/profiling.sampling.html)**
>
> Веб-профилировщик, принимающий вывод в формате Gecko. Подробности использования – в [документации](https://python-all.ru/3.15/library/profiling.sampling.html).
>
> **[FlameGraph](https://python-all.ru/3.15/library/profiling.sampling.html)**
>
> Инструменты для генерации флейм-графов из свёрнутого формата стека.
