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

---

# 27.4. Профилировщики Python

**Исходный код:** [Lib/profile.py](https://python-all.ru/src/3.3/Lib/profile.py) и [Lib/pstats.py](https://python-all.ru/src/3.3/Lib/pstats.py)

---

## 27.4.1. Введение в профилировщики

[`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile) и [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) обеспечивают *детерминированное профилирование* программ Python. *Профиль* – это набор статистики, описывающей, как часто и как долго выполнялись различные части программы. Эту статистику можно преобразовать в отчёты с помощью модуля [`pstats`](https://python-all.ru/3.3/library/profile.html#module-pstats).

Стандартная библиотека Python предоставляет две различные реализации одного и того же интерфейса профилирования:

1. Большинству пользователей рекомендуется [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile); это расширение на C с умеренными накладными расходами, что делает его пригодным для профилирования долго работающих программ. Основан на `lsprof`, предоставленном Бреттом Розеном и Тедом Цоттером.
2. [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) – это чистый модуль Python, чей интерфейс повторяет [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile), но он вносит значительные накладные расходы в профилируемые программы. Если вы пытаетесь расширить профилировщик каким-либо образом, эта задача может оказаться проще с данным модулем.

> **Примечание**
>
> Модули профилировщиков предназначены для получения профиля выполнения данной программы, а не для целей тестирования производительности (для этого существует [`timeit`](https://python-all.ru/3.3/library/timeit.html#module-timeit) для достаточно точных результатов). Это особенно касается сравнения производительности кода Python с кодом C: профилировщики вносят накладные расходы для кода Python, но не для функций уровня C, поэтому код C будет казаться быстрее любого кода Python.

## 27.4.2. Краткое руководство пользователя

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

Чтобы профилировать функцию, принимающую один аргумент, можно сделать следующее:

```python
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
```

(Используйте [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) вместо [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile), если последний недоступен в вашей системе.)

Приведённая команда выполнит [`re.compile()`](https://python-all.ru/3.3/library/re.html#re.compile) и выведет результаты профилирования, как показано ниже:

```python
      197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 re.py:212(compile)
     1    0.000    0.000    0.001    0.001 re.py:268(_compile)
     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)
     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)
     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)
   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)
```

Первая строка показывает, что было отслежено 197 вызовов. Из них 192 были *первичными*, то есть вызов не был вызван рекурсией. Следующая строка: `Ordered by: standard name`, указывает, что текстовая строка в крайнем правом столбце использовалась для сортировки вывода. Заголовки столбцов включают:

**ncalls**

– количество вызовов,

**tottime**

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

**percall**

– результат деления

`tottime`

на

`ncalls`

**cumtime**

– совокупное время, затраченное в этой и всех подфункциях (от вызова до выхода). Это значение точное

*даже*

для рекурсивных функций.

**percall**

– результат деления

`cumtime`

на количество первичных вызовов

**filename:lineno(function)**

– предоставляет соответствующие данные каждой функции

Когда в первом столбце два числа (например, `3/1`), это означает, что функция выполняла рекурсию. Второе значение – количество примитивных вызовов, а первое – общее количество вызовов. Обратите внимание: если функция не рекурсивна, эти два значения совпадают, и печатается только одно число.

Вместо вывода результатов в конце сеанса профилирования их можно сохранить в файл, передав имя файла функции `run()`:

```python
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')
```

Класс [`pstats.Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) читает результаты профилирования из файла и форматирует их различными способами.

Файл [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile) также можно запустить как скрипт для профилирования другого скрипта. Например:

```python
python -m cProfile [-o output_file] [-s sort_order] myscript.py
```

`-o` записывает результаты профилирования в файл вместо stdout

`-s` задаёт одно из значений сортировки [`sort_stats()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.sort_stats) для упорядочивания вывода. Это действует только при отсутствии `-o`.

Класс [`Stats`](https://python-all.ru/3.3/library/profile.html#module-pstats) из модуля [`pstats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) имеет множество методов для обработки и вывода данных, сохранённых в файле результатов профилирования:

```python
import pstats
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()
```

The [`strip_dirs()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.strip_dirs) method removed the extraneous path from all the module names. The [`sort_stats()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.sort_stats) method sorted all the entries according to the standard module/line/name string that is printed. The [`print_stats()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.print_stats) method printed out all the statistics. You might try the following sort calls:

```python
p.sort_stats('name')
p.print_stats()
```

Первый вызов сортирует список по имени функции, а второй вызов выводит статистику. Вот несколько интересных вызовов для экспериментов:

```python
p.sort_stats('cumulative').print_stats(10)
```

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

Чтобы увидеть, какие функции много циклится и занимают много времени, можно сделать:

```python
p.sort_stats('time').print_stats(10)
```

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

Можно также попробовать:

```python
p.sort_stats('file').print_stats('__init__')
```

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

```python
p.sort_stats('time', 'cum').print_stats(.5, 'init')
```

Эта строка сортирует статистику по первичному ключу – времени, и вторичному ключу – совокупному времени, а затем выводит часть статистики. А именно: сначала список уменьшается до 50% (ср. `.5`) от исходного размера, затем сохраняются только строки, содержащие `init`, и выводится этот под-под-список.

Если вам интересно, какие функции вызывали вышеупомянутые функции, теперь (`p` всё ещё отсортирован по последнему критерию) можно выполнить:

```python
p.print_callers(.5, 'init')
```

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

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

```python
p.print_callees()
p.add('restats')
```

При запуске в качестве скрипта модуль [`pstats`](https://python-all.ru/3.3/library/profile.html#module-pstats) представляет собой браузер статистики для чтения и изучения дампов профилирования. Он имеет простой построчный интерфейс (реализованный с помощью [`cmd`](https://python-all.ru/3.3/library/cmd.html#module-cmd)) и интерактивную справку.

## 27.4.3. Справочник модулей [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) и [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile)

Оба модуля [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) и [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile) предоставляют следующие функции:

#### `profile.run(command, filename=None, sort=-1)`

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

```python
exec(command, __main__.__dict__, __main__.__dict__)
```

и собирает статистику профилирования из выполнения. Если имя файла не указано, эта функция автоматически создает экземпляр [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) и выводит простой отчет профилирования. Если указано значение sort, оно передается этому экземпляру [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) для управления сортировкой результатов.

#### `profile.runctx(command, globals, locals, filename=None)`

Эта функция аналогична [`run()`](https://python-all.ru/3.3/library/profile.html#profile.run), с дополнительными аргументами для передачи словарей globals и locals для строки *command*. Эта процедура выполняет:

```python
exec(command, globals, locals)
```

и собирает статистику профилирования, как функция [`run()`](https://python-all.ru/3.3/library/profile.html#profile.run) выше.

#### `class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)`

Этот класс обычно используется только в тех случаях, когда требуется более точный контроль профилирования, чем предоставляет функция `cProfile.run()`.

Можно указать собственный таймер для измерения времени выполнения кода с помощью аргумента *timer*. Это должна быть функция, возвращающая одно число, представляющее текущее время. Если число целое, *timeunit* задаёт множитель, определяющий длительность каждой единицы времени. Например, если таймер возвращает время, измеряемое в тысячных долях секунды, единица времени будет `.001`.

Прямое использование класса [`Profile`](https://python-all.ru/3.3/library/profile.html#profile.Profile) позволяет форматировать результаты профилирования без записи данных профиля в файл:

```python
import cProfile, pstats, io
pr = cProfile.Profile()
pr.enable()
# ... сделать что-то ...
pr.disable()
s = io.StringIO()
sortby = 'cumulative'
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())
```

#### `enable()`

Начинает сбор данных профилирования.

#### `disable()`

Прекращает сбор данных профилирования.

#### `create_stats()`

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

#### `print_stats(sort=-1)`

Создает объект [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) на основе текущего профиля и выводит результаты в stdout.

#### `dump_stats(filename)`

Записать результаты текущего профиля в *filename*.

#### `run(cmd)`

Профилирует cmd с помощью [`exec()`](https://python-all.ru/3.3/library/functions.html#exec).

#### `runctx(cmd, globals, locals)`

Профилирует cmd с помощью [`exec()`](https://python-all.ru/3.3/library/functions.html#exec) с указанным глобальным и локальным окружением.

#### `runcall(func, *args, **kwargs)`

Профилирует `func(*args, **kwargs)`

## 27.4.4. Класс `Stats`

Анализ данных профилировщика выполняется с помощью класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats).

#### `class pstats.Stats(*filenames or profile, stream=sys.stdout)`

Конструктор этого класса создает экземпляр объекта статистики из *filename* (или списка имен файлов) или из экземпляра `Profile`. Вывод будет направлен в поток данных, указанный параметром *stream*.

Файл, выбранный указанным конструктором, должен быть создан соответствующей версией [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) или [`cProfile`](https://python-all.ru/3.3/library/profile.html#module-cProfile). Если конкретнее, то *не* гарантируется совместимость файлов с будущими версиями этого профилировщика, а также нет совместимости с файлами, созданными другими профилировщиками. Если предоставлено несколько файлов, вся статистика для одинаковых функций будет объединена, чтобы в одном отчете можно было рассмотреть общую картину нескольких процессов. Если необходимо объединить дополнительные файлы с данными существующего объекта [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats), можно использовать метод [`add()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.add).

Вместо чтения данных профиля из файла в качестве источника данных профиля можно использовать объект `cProfile.Profile` или [`profile.Profile`](https://python-all.ru/3.3/library/profile.html#profile.Profile).

Объекты [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) имеют следующие методы:

#### `strip_dirs()`

Этот метод класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) удаляет всю информацию о ведущих путях из имен файлов. Это очень полезно для уменьшения размера вывода, чтобы он помещался (почти) в 80 столбцов. Этот метод изменяет объект, и удаленная информация теряется. После операции удаления считается, что записи в объекте находятся в «случайном» порядке, как сразу после инициализации и загрузки объекта. Если [`strip_dirs()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.strip_dirs) приводит к тому, что два имени функций становятся неразличимыми (они находятся на одной строке одного файла и имеют одинаковое имя функции), то статистика для этих двух записей накапливается в одной записи.

#### `add(*filenames)`

Этот метод класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) накапливает дополнительную информацию профилирования в текущем объекте профилирования. Его аргументы должны ссылаться на файлы, созданные соответствующей версией [`profile.run()`](https://python-all.ru/3.3/library/profile.html#profile.run) или `cProfile.run()`. Статистика для функций с одинаковыми именами (относительно: файл, строка, имя) автоматически накапливается в одну статистику функции.

#### `dump_stats(filename)`

Сохраняет данные, загруженные в объект [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats), в файл с именем *filename*. Файл создается, если он не существует, и перезаписывается, если он уже существует. Это эквивалентно одноименному методу классов [`profile.Profile`](https://python-all.ru/3.3/library/profile.html#profile.Profile) и `cProfile.Profile`.

#### `sort_stats(*keys)`

Этот метод изменяет объект [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats), сортируя его по заданным критериям. Аргументом обычно служит строка, определяющая основу сортировки (например: `'time'` или `'name'`).

Если указано несколько ключей, то дополнительные используются как вторичные критерии, когда все предыдущие ключи равны. Например, `sort_stats('name', 'file')` отсортирует все записи по имени функции, а совпадения (одинаковые имена функций) разрешит сортировкой по имени файла.

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

| Допустимый аргумент | Значение |
| --- | --- |
| `'calls'` | количество вызовов |
| `'cumulative'` | совокупное время |
| `'cumtime'` | совокупное время |
| `'file'` | имя файла |
| `'filename'` | имя файла |
| `'module'` | имя файла |
| `'ncalls'` | количество вызовов |
| `'pcalls'` | количество примитивных вызовов |
| `'line'` | номер строки |
| `'name'` | имя функции |
| `'nfl'` | имя/файл/строка |
| `'stdname'` | стандартное имя |
| `'time'` | внутреннее время |
| `'tottime'` | внутреннее время |

Обратите внимание: все сортировки по статистике выполняются по убыванию (сначала самые затратные по времени элементы), тогда как поиск по имени, файлу и номеру строки – по возрастанию (в алфавитном порядке). Тонкое различие между `'nfl'` и `'stdname'` в том, что стандартное имя – это сортировка по напечатанному имени, а это означает, что встроенные номера строк сравниваются необычным образом. Например, строки 3, 20 и 40 (при одинаковых именах файлов) расположились бы в строковом порядке как 20, 3, 40. В отличие от этого, `'nfl'` выполняет численное сравнение номеров строк. Фактически, `sort_stats('nfl')` – это то же самое, что `sort_stats('name', 'file', 'line')`.

По соображениям обратной совместимости допускаются числовые аргументы `-1`, `0`, `1` и `2`. Они интерпретируются соответственно как `'stdname'`, `'calls'`, `'time'` и `'cumulative'`. Если используется этот старый формат (числовой), то будет применён только один ключ сортировки (числовой), а остальные аргументы будут молча проигнорированы.

#### `reverse_order()`

Этот метод класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) меняет порядок базового списка внутри объекта на обратный. Учтите, что по умолчанию порядок по возрастанию или убыванию выбирается правильно в зависимости от выбранного ключа сортировки.

#### `print_stats(*restrictions)`

Этот метод класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) выводит отчёт, как описано в определении [`profile.run()`](https://python-all.ru/3.3/library/profile.html#profile.run).

Порядок вывода основан на последней операции [`sort_stats()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.sort_stats), выполненной над объектом (с учётом оговорок в [`add()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.add) и [`strip_dirs()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.strip_dirs)).

Переданные аргументы (если есть) позволяют ограничить список значимыми записями. Изначально список включает все профилированные функции. Каждое ограничение – это либо целое число (чтобы выбрать количество строк), либо десятичная дробь от 0.0 до 1.0 включительно (чтобы выбрать процент строк), либо регулярное выражение (для сопоставления с выводимым стандартным именем). Если указано несколько ограничений, они применяются последовательно. Например:

```python
print_stats(.1, 'foo:')
```

сначала ограничит вывод первыми 10% списка, а затем выведет только те функции, которые входят в имя файла `.*foo:`. В отличие от этого, команда:

```python
print_stats('foo:', .1)
```

ограничит список всеми функциями с именами файлов `.*foo:`, а затем выведет только первые 10% из них.

#### `print_callers(*restrictions)`

Этот метод класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) выводит список всех функций, которые вызывали каждую функцию из профилированной базы данных. Порядок совпадает с [`print_stats()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.print_stats), и определение аргумента-ограничения также совпадает. Каждый вызывающий выводится на отдельной строке. Формат немного отличается в зависимости от профилировщика, создавшего статистику:

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

#### `print_callees(*restrictions)`

Этот метод для класса [`Stats`](https://python-all.ru/3.3/library/profile.html#pstats.Stats) выводит список всех функций, которые были вызваны указанной функцией. За исключением этого изменения направления вызовов (т.е. «вызванные» вместо «вызываемые»), аргументы и порядок сортировки идентичны методу [`print_callers()`](https://python-all.ru/3.3/library/profile.html#pstats.Stats.print_callers).

## 27.4.5. Что такое детерминированное профилирование?

*Deterministic profiling* is meant to reflect the fact that all *function call*, *function return*, and *exception* events are monitored, and precise timings are made for the intervals between these events (during which time the user’s code is executing). In contrast, *statistical profiling* (which is not done by this module) randomly samples the effective instruction pointer, and deduces where time is being spent. The latter technique traditionally involves less overhead (as the code does not need to be instrumented), but provides only relative indications of where time is being spent.

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

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

## 27.4.6. Ограничения

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

The second problem is that it “takes a while” from when an event is dispatched until the profiler’s call to get the time actually *gets* the state of the clock. Similarly, there is a certain lag when exiting the profiler event handler from the time that the clock’s value was obtained (and then squirreled away), until the user’s code is once again executing. As a result, functions that are called many times, or call many functions, will typically accumulate this error. The error that accumulates in this fashion is typically less than the accuracy of the clock (less than one clock tick), but it *can* accumulate and become very significant.

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

## 27.4.7. Калибровка

Профилировщик модуля [`profile`](https://python-all.ru/3.3/library/profile.html#module-profile) вычитает константу из времени обработки каждого события, чтобы скомпенсировать накладные расходы на вызов функции времени и сохранение результатов. По умолчанию константа равна 0. Следующая процедура позволяет получить более точную константу для конкретной платформы (см. [*Limitations*](https://python-all.ru/3.3/library/profile.html#profile-limitations)).

```python
import profile
pr = profile.Profile()
for i in range(5):
    print(pr.calibrate(10000))
```

Метод выполняет указанное аргументом количество вызовов Python напрямую и снова под профилировщиком, измеряя время для обоих случаев. Затем он вычисляет скрытые накладные расходы на каждое событие профилировщика и возвращает их в виде числа с плавающей точкой. Например, на 1.8 ГГц Intel Core i5 под управлением Mac OS X с использованием time.clock() в качестве таймера магическое число составляет около 4.04e-6.

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

Когда получен стабильный результат, его можно использовать тремя способами:

```python
import profile

# 1. Применить вычисленное смещение ко всем экземплярам Profile, создаваемым в дальнейшем.
profile.Profile.bias = your_computed_bias

# 2. Применить вычисленное смещение к конкретному экземпляру Profile.
pr = profile.Profile()
pr.bias = your_computed_bias

# 3. Указать вычисленное смещение в конструкторе экземпляра.
pr = profile.Profile(bias=your_computed_bias)
```

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

## 27.4.8. Использование пользовательского таймера

Если требуется изменить способ определения текущего времени (например, принудительно использовать настенное время или прошедшее процессорное время), передайте нужную функцию замера времени конструктору класса `Profile`:

```python
pr = profile.Profile(your_time_func)
```

Полученный профилировщик будет вызывать `your_time_func`. В зависимости от того, используется [`profile.Profile`](https://python-all.ru/3.3/library/profile.html#profile.Profile) или `cProfile.Profile`, возвращаемое значение `your_time_func` будет интерпретироваться по-разному:

**[`profile.Profile`](https://python-all.ru/3.3/library/profile.html#profile.Profile)**

`your_time_func` должна возвращать одно число или список чисел, сумма которых равна текущему времени (как возвращает [`os.times()`](https://python-all.ru/3.3/library/os.html#os.times)). Если функция возвращает одно число или список длиной 2, то будет использована особенно быстрая версия диспетчерской подпрограммы.

Следует иметь в виду, что перед использованием нужно откалибровать класс профилировщика под выбранную функцию замера времени (см. [*Calibration*](https://python-all.ru/3.3/library/profile.html#profile-calibration)). Для большинства машин таймер, возвращающий одно целое число, даёт наилучшие результаты с точки зрения низких накладных расходов при профилировании. ([`os.times()`](https://python-all.ru/3.3/library/os.html#os.times) *достаточно* плох, так как возвращает кортеж чисел с плавающей запятой). Если требуется заменить таймер наиболее чистым способом, создайте производный класс и жёстко пропишите заменяющий метод диспетчеризации, который наилучшим образом обрабатывает вызов вашего таймера, вместе с подходящей константой калибровки.

**`cProfile.Profile`**

`your_time_func` должна возвращать одно число. Если она возвращает целые числа, можно также вызвать конструктор класса со вторым аргументом, задающим реальную длительность одной единицы времени. Например, если `your_integer_time_func` возвращает время, измеренное в тысячных долях секунды, экземпляр `Profile` создаётся следующим образом:

```python
pr = cProfile.Profile(your_integer_time_func, 0.001)
```

Поскольку класс `cProfile.Profile` невозможно откалибровать, пользовательские функции замера времени следует использовать с осторожностью, и они должны быть максимально быстрыми. Для наилучших результатов с пользовательским таймером может потребоваться жёстко прописать его в C-исходниках внутреннего модуля `_lsprof`.

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