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

profile.md

443 строк · 47.2 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3.9/library/profile.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Профилировщики Python89**Исходный код:** [Lib/profile.py](https://python-all.ru/src/3.9/Lib/profile.py) и [Lib/pstats.py](https://python-all.ru/src/3.9/Lib/pstats.py)1011---1213## Введение в профилировщики1415[`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) и [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) предоставляют *детерминированное профилирование* программ Python. *Профиль* – это набор статистики, описывающий, как часто и как долго выполнялись различные части программы. Эти статистические данные могут быть отформатированы в отчёты с помощью модуля [`pstats`](https://python-all.ru/3.9/library/profile.html#module-pstats).1617Стандартная библиотека Python предоставляет две различные реализации одного и того же интерфейса профилирования:18191. [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) рекомендуется для большинства пользователей; это расширение на C с приемлемыми накладными расходами, что делает его подходящим для профилирования длительно работающих программ. Основан на `lsprof`, предоставленном Бреттом Розеном и Тедом Чоттером.202. [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile), чисто Python-модуль, чей интерфейс имитируется [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile), но который добавляет значительные накладные расходы профилируемым программам. Если требуется расширить профилировщик каким-либо образом, задача может оказаться проще с этим модулем. Изначально спроектирован и написан Джимом Роскиндом.2122> **Примечание**23>24> Модули профилирования предназначены для получения профиля выполнения данной программы, а не для целей бенчмаркинга (для этого существует [`timeit`](https://python-all.ru/3.9/library/timeit.html#module-timeit) для достаточно точных результатов). Это особенно относится к сравнению производительности Python-кода с C-кодом: профилировщики добавляют накладные расходы для Python-кода, но не для C-функций, поэтому C-код будет казаться быстрее любого Python-кода.2526## Краткое руководство пользователя2728Этот раздел предназначен для пользователей, которые «не хотят читать руководство». В нём даётся очень краткий обзор, и он позволяет быстро выполнить профилирование существующего приложения.2930Чтобы профилировать функцию, принимающую один аргумент, можно сделать следующее:3132```python33import cProfile34import re35cProfile.run('re.compile("foo|bar")')36```3738(Use [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) instead of [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) if the latter is not available on your system.)3940Приведённое выше действие запустит [`re.compile()`](https://python-all.ru/3.9/library/re.html#re.compile) и выведет результаты профилирования, как показано ниже:4142```python43      197 function calls (192 primitive calls) in 0.002 seconds4445Ordered by: standard name4647ncalls  tottime  percall  cumtime  percall filename:lineno(function)48     1    0.000    0.000    0.001    0.001 <string>:1(<module>)49     1    0.000    0.000    0.001    0.001 re.py:212(compile)50     1    0.000    0.000    0.001    0.001 re.py:268(_compile)51     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)52     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)53     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)54   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)55```5657Первая строка показывает, что было отслежено 197 вызовов. Из них 192 были *примитивными*, то есть не вызваны рекурсией. Следующая строка: `Ordered by: standard name`, указывает, что текстовая строка в крайнем правом столбце использовалась для сортировки вывода. Заголовки столбцов включают:5859**ncalls**6061– количество вызовов.6263**tottime**6465– общее время, затраченное в данной функции (исключая время, затраченное в вызовах подфункций).6667**percall**6869– частное от деления `tottime` на `ncalls`7071**cumtime**7273– совокупное время, затраченное в этой и всех подфункциях (от вызова до выхода). Это значение точное *даже* для рекурсивных функций.7475**percall**7677– частное от деления `cumtime` на количество примитивных вызовов7879**filename:lineno(function)**8081– предоставляет соответствующие данные каждой функции8283Когда в первом столбце два числа (например, `3/1`), это означает, что функция выполняла рекурсию. Второе значение – количество примитивных вызовов, а первое – общее количество вызовов. Обратите внимание: если функция не рекурсивна, эти два значения совпадают, и печатается только одно число.8485Вместо вывода результатов в конце сеанса профилирования можно сохранить результаты в файл, указав имя файла функции `run()`:8687```python88import cProfile89import re90cProfile.run('re.compile("foo|bar")', 'restats')91```9293Класс [`pstats.Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) читает результаты профилирования из файла и форматирует их различными способами.9495Файлы [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) и [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) также можно запускать как скрипты для профилирования другого скрипта. Например:9697```python98python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)99```100101`-o` записывает результаты профилирования в файл, а не в stdout102103`-s` задаёт одно из значений сортировки [`sort_stats()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.sort_stats) для сортировки вывода. Применяется, только если `-o` не указан.104105`-m` указывает, что профилируется модуль, а не скрипт.106107> Новое в версии 3.7: Добавлена опция `-m` в [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile).108>109> Новое в версии 3.8: Добавлена опция `-m` в [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile).110111Класс [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) модуля [`pstats`](https://python-all.ru/3.9/library/profile.html#module-pstats) имеет различные методы для обработки и вывода данных, сохранённых в файл результатов профилирования:112113```python114import pstats115from pstats import SortKey116p = pstats.Stats('restats')117p.strip_dirs().sort_stats(-1).print_stats()118```119120Метод [`strip_dirs()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.strip_dirs) удаляет лишние пути из всех имён модулей. Метод [`sort_stats()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.sort_stats) сортирует все записи по стандартной строке модуль/строка/имя, которая выводится. Метод [`print_stats()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.print_stats) выводит всю статистику. Можно попробовать следующие вызовы сортировки:121122```python123p.sort_stats(SortKey.NAME)124p.print_stats()125```126127Первый вызов сортирует список по имени функции, а второй вызов выводит статистику. Вот несколько интересных вызовов для экспериментов:128129```python130p.sort_stats(SortKey.CUMULATIVE).print_stats(10)131```132133Это сортирует профиль по суммарному времени в функции и затем выводит только десять наиболее значимых строк. Чтобы понять, какие алгоритмы занимают время, следует использовать приведённую выше строку.134135Чтобы увидеть, какие функции много циклится и занимают много времени, можно сделать:136137```python138p.sort_stats(SortKey.TIME).print_stats(10)139```140141для сортировки по времени, затраченному внутри каждой функции, и вывода статистики для десяти первых функций.142143Можно также попробовать:144145```python146p.sort_stats(SortKey.FILENAME).print_stats('__init__')147```148149Это отсортирует всю статистику по имени файла и затем выведет статистику только для методов инициализации классов (поскольку они содержат `__init__` в названии). В качестве последнего примера можно попробовать:150151```python152p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')153```154155Эта строка сортирует статистику сначала по времени, затем по суммарному времени, и выводит часть статистики. Если точнее, список сначала сокращается до 50% (отн. `.5`) от исходного размера, затем оставляются только строки, содержащие `init`, и выводится этот под-под-список.156157Если интересно, какие функции вызывали вышеуказанные функции, теперь (`p` всё ещё отсортирован по последнему критерию) можно сделать:158159```python160p.print_callers(.5, 'init')161```162163и будет получен список вызывающих для каждой из перечисленных функций.164165Для более широких возможностей придётся прочитать руководство или догадаться, что делают следующие функции:166167```python168p.print_callees()169p.add('restats')170```171172При запуске как скрипт, модуль [`pstats`](https://python-all.ru/3.9/library/profile.html#module-pstats) является браузером статистики для чтения и просмотра дампов профилирования. Он имеет простой построчный интерфейс (реализованный с помощью [`cmd`](https://python-all.ru/3.9/library/cmd.html#module-cmd)) и интерактивную справку.173174## [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) и [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) Справочник по модулям175176Оба модуля [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) и [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) предоставляют следующие функции:177178#### `profile.run(command, filename=None, sort=-1)`179180Эта функция принимает один аргумент, который можно передать функции [`exec()`](https://python-all.ru/3.9/library/functions.html#exec), и необязательное имя файла. Во всех случаях эта процедура выполняет:181182```python183exec(command, __main__.__dict__, __main__.__dict__)184```185186и собирает статистику профилирования из выполнения. Если имя файла не указано, эта функция автоматически создаёт экземпляр [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) и выводит простой отчёт профилирования. Если указано значение сортировки, оно передаётся этому экземпляру [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) для управления сортировкой результатов.187188#### `profile.runctx(command, globals, locals, filename=None, sort=-1)`189190Эта функция аналогична [`run()`](https://python-all.ru/3.9/library/profile.html#profile.run), но с дополнительными аргументами для передачи словарей globals и locals для строки *command*. Данная процедура выполняет:191192```python193exec(command, globals, locals)194```195196и собирает статистику профилирования, как в функции [`run()`](https://python-all.ru/3.9/library/profile.html#profile.run) выше.197198#### `class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)`199200Этот класс обычно используется только тогда, когда требуется более точный контроль профилирования, чем предоставляет функция `cProfile.run()`.201202Можно указать собственный таймер для измерения времени выполнения кода с помощью аргумента *timer*. Это должна быть функция, возвращающая одно число, представляющее текущее время. Если число целое, *timeunit* задаёт множитель, определяющий длительность каждой единицы времени. Например, если таймер возвращает время, измеряемое в тысячных долях секунды, единица времени будет `.001`.203204Непосредственное использование класса [`Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile) позволяет форматировать результаты профилирования без записи данных профиля в файл:205206```python207import cProfile, pstats, io208from pstats import SortKey209pr = cProfile.Profile()210pr.enable()211# ... сделать что-то ...212pr.disable()213s = io.StringIO()214sortby = SortKey.CUMULATIVE215ps = pstats.Stats(pr, stream=s).sort_stats(sortby)216ps.print_stats()217print(s.getvalue())218```219220Класс [`Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile) также можно использовать как контекстный менеджер (поддерживается только в модуле [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile); см. [Контекстные менеджеры](https://python-all.ru/3.9/library/stdtypes.html#typecontextmanager)):221222```python223import cProfile224225with cProfile.Profile() as pr:226    # ... сделать что-то ...227228pr.print_stats()229```230231Изменено в версии 3.8: Добавлена поддержка контекстного менеджера.232233#### `enable()`234235Начать сбор данных профилирования. Только в [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile).236237#### `disable()`238239Остановить сбор данных профилирования. Только в [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile).240241#### `create_stats()`242243Остановить сбор данных профилирования и сохранить результаты внутри как текущий профиль.244245#### `print_stats(sort=-1)`246247Создать объект [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) на основе текущего профиля и вывести результаты в stdout.248249#### `dump_stats(filename)`250251Записать результаты текущего профиля в *filename*.252253#### `run(cmd)`254255Профилировать cmd с помощью [`exec()`](https://python-all.ru/3.9/library/functions.html#exec).256257#### `runctx(cmd, globals, locals)`258259Профилировать cmd с помощью [`exec()`](https://python-all.ru/3.9/library/functions.html#exec) с указанным глобальным и локальным окружением.260261#### `runcall(func, /, *args, **kwargs)`262263Профилировать `func(*args, **kwargs)`264265Обратите внимание, что профилирование будет работать, только если вызванная команда/функция действительно возвращает управление. Если интерпретатор завершается (например, вызовом [`sys.exit()`](https://python-all.ru/3.9/library/sys.html#sys.exit) во время выполнения вызванной команды/функции), результаты профилирования не будут выведены.266267## Класс `Stats`268269Анализ данных профилировщика выполняется с помощью класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats).270271#### `class pstats.Stats(*filenames or profile, stream=sys.stdout)`272273Конструктор этого класса создаёт экземпляр «объекта статистики» из *filename* (или списка имён файлов) или из экземпляра `Profile`. Вывод будет направлен в поток данных, указанный параметром *stream*.274275Файл, указанный в конструкторе, должен быть создан соответствующей версией [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) или [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile). Если точнее, *нет* гарантии совместимости файлов с будущими версиями этого профилировщика, а также нет совместимости с файлами, созданными другими профилировщиками или тем же профилировщиком в другой операционной системе. Если передано несколько файлов, вся статистика для одинаковых функций объединяется, чтобы в одном отчёте можно было получить общую картину по нескольким процессам. Если необходимо объединить дополнительные файлы с данными существующего объекта [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats), можно использовать метод [`add()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.add).276277Вместо чтения данных профиля из файла можно использовать объект `cProfile.Profile` или [`profile.Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile) в качестве источника данных профиля.278279Объекты [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) имеют следующие методы:280281#### `strip_dirs()`282283Этот метод класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) удаляет всю информацию о начальных путях из имён файлов. Он очень полезен для уменьшения размера вывода, чтобы он помещался (примерно) в 80 столбцов. Этот метод изменяет объект, и удалённая информация теряется. После выполнения операции удаления пути записи объекта считаются находящимися в «случайном» порядке, как сразу после инициализации и загрузки объекта. Если [`strip_dirs()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.strip_dirs) приводит к тому, что два имени функций становятся неразличимыми (они находятся на одной строке одного и того же файла и имеют одинаковое имя функции), то статистика для этих двух записей объединяется в одну запись.284285#### `add(*filenames)`286287Этот метод класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) накапливает дополнительную информацию профилирования в текущем объекте профилирования. Его аргументы должны указывать на имена файлов, созданные соответствующей версией [`profile.run()`](https://python-all.ru/3.9/library/profile.html#profile.run) или `cProfile.run()`. Статистика для функций с одинаковыми именами (файл, строка, имя) автоматически накапливается в единую статистику функции.288289#### `dump_stats(filename)`290291Сохраняет данные, загруженные в объект [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats), в файл с именем *filename*. Файл создаётся, если его не существует, и перезаписывается, если он уже существует. Это эквивалентно одноимённому методу классов [`profile.Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile) и `cProfile.Profile`.292293#### `sort_stats(*keys)`294295Этот метод изменяет объект [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats), сортируя его по заданным критериям. Аргумент может быть строкой или перечислением SortKey, определяющим основание для сортировки (например: `'time'`, `'name'`, `SortKey.TIME` или `SortKey.NAME`). Аргумент-перечисление SortKey имеет преимущество перед строковым аргументом, так как он более надёжен и менее подвержен ошибкам.296297Если указано более одного ключа, то дополнительные ключи используются как вторичные критерии, когда все предыдущие ключи равны. Например, `sort_stats(SortKey.NAME, SortKey.FILE)` отсортирует все записи по имени функции, а при совпадении имён (одинаковые имена функций) разрешит связи сортировкой по имени файла.298299Для строкового аргумента можно использовать сокращения для любых имён ключей, если сокращение однозначно.300301Ниже приведены допустимые строки и значения SortKey:302303| Допустимый строковый аргумент | Допустимый аргумент-перечисление | Значение |304| --- | --- | --- |305| `'calls'` | SortKey.CALLS | количество вызовов |306| `'cumulative'` | SortKey.CUMULATIVE | совокупное время |307| `'cumtime'` | Н/Д | совокупное время |308| `'file'` | Н/Д | имя файла |309| `'filename'` | Н/Д | имя файла |310| `'module'` | Н/Д | имя файла |311| `'ncalls'` | Н/Д | количество вызовов |312| `'pcalls'` | SortKey.PCALLS | количество примитивных вызовов |313| `'line'` | SortKey.LINE | номер строки |314| `'name'` | SortKey.NAME | имя функции |315| `'nfl'` | SortKey.NFL | имя/файл/строка |316| `'stdname'` | SortKey.STDNAME | стандартное имя |317| `'time'` | SortKey.TIME | внутреннее время |318| `'tottime'` | Н/Д | внутреннее время |319320Обратите внимание, что сортировки по статистике выполняются по убыванию (на первом месте наиболее затратные по времени элементы), тогда как поиск по имени, файлу и номеру строки – по возрастанию (в алфавитном порядке). Тонкое различие между `SortKey.NFL` и `SortKey.STDNAME` заключается в том, что стандартное имя – это сортировка имени в том виде, в котором оно печатается, то есть встроенные номера строк сравниваются необычным образом. Например, строки 3, 20 и 40 будут (если имена файлов одинаковы) отображаться в строковом порядке 20, 3 и 40. Напротив, `SortKey.NFL` выполняет численное сравнение номеров строк. Фактически, `sort_stats(SortKey.NFL)` – это то же самое, что и `sort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE)`.321322По причинам обратной совместимости допускаются числовые аргументы `-1`, `0`, `1` и `2`. Они интерпретируются как `'stdname'`, `'calls'`, `'time'` и `'cumulative'` соответственно. Если используется старый формат (числовой), будет использоваться только один ключ сортировки (числовой), а дополнительные аргументы будут молча игнорироваться.323324Новое в версии 3.7: Добавлено перечисление SortKey.325326#### `reverse_order()`327328Этот метод для класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) меняет порядок основного списка внутри объекта. Обратите внимание, что по умолчанию порядок (по возрастанию или убыванию) выбирается правильно на основе выбранного ключа сортировки.329330#### `print_stats(*restrictions)`331332Этот метод для класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) выводит отчёт, как описано в определении [`profile.run()`](https://python-all.ru/3.9/library/profile.html#profile.run).333334Порядок вывода основан на последней операции [`sort_stats()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.sort_stats), выполненной над объектом (с учётом оговорок в [`add()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.add) и [`strip_dirs()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.strip_dirs)).335336Переданные аргументы (если есть) можно использовать для ограничения списка значимыми записями. Изначально список считается полным набором профилированных функций. Каждое ограничение – это либо целое число (для выбора количества строк), либо десятичная дробь от 0.0 до 1.0 включительно (для выбора процента строк), либо строка, которая будет интерпретироваться как регулярное выражение (для сопоставления с образцом стандартного имени, которое выводится). Если указано несколько ограничений, они применяются последовательно. Например:337338```python339print_stats(.1, 'foo:')340```341342сначала ограничит вывод первыми 10% списка, а затем напечатает только функции, относящиеся к файлу с именем `.*foo:`. В отличие от этого, команда:343344```python345print_stats('foo:', .1)346```347348ограничит список всеми функциями, имеющими имена файлов `.*foo:`, а затем выведет только первые 10% из них.349350#### `print_callers(*restrictions)`351352Этот метод для класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) выводит список всех функций, которые вызывали каждую функцию в профилированной базе данных. Порядок такой же, как у [`print_stats()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.print_stats), и определение аргумента-ограничения тоже идентично. Каждая вызывающая функция выводится на отдельной строке. Формат немного различается в зависимости от профилировщика, который создал статистику:353354- С [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) после каждой вызывающей функции в скобках указывается число, показывающее, сколько раз был выполнен этот конкретный вызов. Для удобства второе число без скобок повторяет суммарное время, затраченное в функции, справа.355- С [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) перед каждой вызывающей функцией указываются три числа: количество раз, когда был выполнен этот конкретный вызов, а также общее и суммарное время, затраченное в текущей функции при её вызове данной вызывающей функцией.356357#### `print_callees(*restrictions)`358359Этот метод для класса [`Stats`](https://python-all.ru/3.9/library/profile.html#pstats.Stats) выводит список всех функций, которые были вызваны указанной функцией. За исключением этого изменения направления вызовов (вызывала vs была вызвана), аргументы и порядок идентичны методу [`print_callers()`](https://python-all.ru/3.9/library/profile.html#pstats.Stats.print_callers).360361#### `get_stats_profile()`362363Этот метод возвращает экземпляр StatsProfile, который содержит отображение имён функций в экземпляры FunctionProfile. Каждый экземпляр FunctionProfile содержит информацию о профиле функции, такую как время выполнения функции, количество её вызовов и т.д.364365Новое в версии 3.9: Добавлены следующие классы данных: StatsProfile, FunctionProfile. Добавлена следующая функция: get\_stats\_profile.366367## Что такое детерминированное профилирование?368369*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.370371В Python, поскольку во время выполнения активен интерпретатор, для детерминированного профилирования не требуется инструментированный код. Python автоматически предоставляет *ловушку (хук)* (необязательный колбэк) для каждого события. Кроме того, интерпретируемый характер Python добавляет столько накладных расходов на выполнение, что детерминированное профилирование в типичных приложениях добавляет лишь небольшие вычислительные накладные расходы. В результате детерминированное профилирование не так затратно, но при этом предоставляет обширную статистику времени выполнения программы Python.372373Статистику количества вызовов можно использовать для выявления ошибок в коде (неожиданные количества) и для определения возможных точек встраивания (высокое количество вызовов). Статистику внутреннего времени можно использовать для выявления «горячих циклов», которые следует тщательно оптимизировать. Статистику суммарного времени следует использовать для выявления высокоуровневых ошибок в выборе алгоритмов. Обратите внимание, что нестандартная обработка суммарного времени в этом профилировщике позволяет напрямую сравнивать статистику рекурсивных реализаций алгоритмов с итеративными.374375## Ограничения376377Одно ограничение связано с точностью информации о времени. Существует фундаментальная проблема с детерминированными профилировщиками, касающаяся точности. Наиболее очевидное ограничение состоит в том, что внутренние «часы» тикают с частотой (обычно) около 0,001 секунды. Следовательно, ни одно измерение не может быть точнее этих часов. Если сделать достаточно измерений, «ошибка» будет стремиться к усреднению. К сожалению, устранение этой первой ошибки приводит ко второму источнику ошибок.378379The 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.380381Эта проблема более важна для [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile), чем для [`cProfile`](https://python-all.ru/3.9/library/profile.html#module-cProfile) с меньшими накладными расходами. По этой причине [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) предоставляет возможность калибровки для заданной платформы, чтобы эту ошибку можно было вероятностно (в среднем) устранить. После калибровки профилировщика он становится точнее (в смысле наименьших квадратов), но иногда выдаёт отрицательные числа (когда количество вызовов исключительно мало, и боги вероятности против вас :-)). Не *стоит пугаться* отрицательных чисел в профиле. Они должны *появляться только*, если вы откалибровали профилировщик, и результаты на самом деле лучше, чем без калибровки.382383## Калибровка384385Профилировщик модуля [`profile`](https://python-all.ru/3.9/library/profile.html#module-profile) вычитает константу из времени обработки каждого события, чтобы компенсировать накладные расходы на вызов функции времени и сохранение результатов. По умолчанию константа равна 0. Следующую процедуру можно использовать для получения лучшей константы для данной платформы (см. [Ограничения](https://python-all.ru/3.9/library/profile.html#profile-limitations)).386387```python388import profile389pr = profile.Profile()390for i in range(5):391    print(pr.calibrate(10000))392```393394Метод выполняет количество вызовов Python, заданное аргументом, напрямую и снова под профилировщиком, измеряя время для обоих случаев. Затем он вычисляет скрытые накладные расходы на одно событие профилировщика и возвращает это значение в виде числа с плавающей запятой. Например, на процессоре Intel Core i5 с частотой 1,8 ГГц под управлением macOS и с использованием time.process\_time() в Python в качестве таймера магическое число составляет около 4,04e-6.395396Цель этого упражнения – получить достаточно стабильный результат. Если ваш компьютер *очень* быстрый или функция таймера имеет низкое разрешение, возможно, придётся передать 100000 или даже 1000000, чтобы получить стабильные результаты.397398Когда получен стабильный результат, его можно использовать тремя способами:399400```python401import profile402403# 1. Применить вычисленное смещение ко всем экземплярам Profile, создаваемым в дальнейшем.404profile.Profile.bias = your_computed_bias405406# 2. Применить вычисленное смещение к конкретному экземпляру Profile.407pr = profile.Profile()408pr.bias = your_computed_bias409410# 3. Указать вычисленное смещение в конструкторе экземпляра.411pr = profile.Profile(bias=your_computed_bias)412```413414Если есть выбор, лучше выбрать меньшую константу – тогда результаты будут «реже» отображаться как отрицательные в статистике профилирования.415416## Использование пользовательского таймера417418Если требуется изменить способ определения текущего времени (например, принудительно использовать wall-clock time или время процесса), передайте нужную функцию таймера в конструктор класса `Profile`:419420```python421pr = profile.Profile(your_time_func)422```423424Полученный профилировщик будет вызывать `your_time_func`. В зависимости от того, используется ли [`profile.Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile) или `cProfile.Profile`, возвращаемое значение `your_time_func` будет интерпретироваться по-разному:425426**[`profile.Profile`](https://python-all.ru/3.9/library/profile.html#profile.Profile)**427428`your_time_func` должна возвращать одно число или список чисел, сумма которых равна текущему времени (как [`os.times()`](https://python-all.ru/3.9/library/os.html#os.times)). Если функция возвращает одно число времени или длина возвращаемого списка равна 2, то будет использована особенно быстрая версия диспетчерской подпрограммы.429430Следует учесть, что необходимо откалибровать класс профилировщика для выбранной функции таймера (см. [Калибровка](https://python-all.ru/3.9/library/profile.html#profile-calibration)). Для большинства машин таймер, возвращающий одно целое число, даёт наилучшие результаты с точки зрения низких накладных расходов при профилировании. ([`os.times()`](https://python-all.ru/3.9/library/os.html#os.times) *довольно* плох, так как он возвращает кортеж значений с плавающей запятой). Если требуется заменить лучший таймер наиболее чистым способом, создайте подкласс и жёстко пропишите метод диспетчеризации, который наилучшим образом обрабатывает вызов вашего таймера, вместе с соответствующей константой калибровки.431432**`cProfile.Profile`**433434`your_time_func` должна возвращать одно число. Если она возвращает целые числа, можно также вызвать конструктор класса со вторым аргументом, указывающим реальную длительность одной единицы времени. Например, если `your_integer_time_func` возвращает время, измеряемое в тысячах секунд, экземпляр `Profile` конструируется следующим образом:435436```python437pr = cProfile.Profile(your_integer_time_func, 0.001)438```439440Поскольку класс `cProfile.Profile` не может быть откалиброван, пользовательские функции таймера следует использовать с осторожностью, и они должны быть максимально быстрыми. Для достижения наилучших результатов с пользовательским таймером может потребоваться жёстко задать его в исходном коде C внутреннего модуля `_lsprof`.441442Python 3.3 добавляет несколько новых функций в [`time`](https://python-all.ru/3.9/library/time.html#module-time), которые можно использовать для точных измерений процессорного или реального времени. Например, см. [`time.perf_counter()`](https://python-all.ru/3.9/library/time.html#time.perf_counter).443