Содержание страницы
profile – Профилировщик на чистом Python¶profile – Pure Python profiler
Исходный код: Lib/profile.py
Устарело с версии 3.15, будет удалено в версии 3.17.
Модуль profile устарел и будет удалён в Python 3.17.
Используйте вместо него profiling.tracing.
Модуль profile содержит реализацию детерминированного профилировщика на чистом Python. Хотя он может пригодиться для изучения внутреннего устройства профилировщика или расширения его поведения через наследование, его реализация на чистом Python вносит значительные накладные расходы по сравнению с C-модулем profiling.tracing.
Для большинства задач профилирования используйте:
profiling.samplingдля отладки в продакшене с нулевыми накладными расходамиprofiling.tracingдля разработки и тестирования
Миграция¶Migration
Переход с profile на profiling.tracing не представляет сложности.
API совместимы:
# Старое (устарело)
import profile
profile.run('my_function()')
# Новое (рекомендуется)
import profiling.tracing
profiling.tracing.run('my_function()')
В большинстве случаев достаточно заменить import profile на import profiling.tracing
(и использовать profiling.tracing вместо profile по всему коду) –
это прямой путь миграции.
Примечание
Модуль cProfile остаётся доступным как обратно совместимый псевдоним
profiling.tracing. Существующий код, использующий import cProfile,
продолжит работу без изменений.
profile и profiling.tracing – справочник по модулям¶profile and profiling.tracing module reference
Оба модуля, profile и profiling.tracing, предоставляют
следующие функции:
- profile.run(command, filename=None, sort=-1)¶
Эта функция принимает один аргумент, который можно передать функции
exec(), и необязательное имя файла. Во всех случаях эта процедура выполняет:exec(command, __main__.__dict__, __main__.__dict__)
и собирает статистику профилирования из выполнения. Если имя файла не указано, эта функция автоматически создаёт экземпляр
Statsи выводит простой отчёт профилирования. Если указано значение сортировки, оно передаётся этому экземпляруStatsдля управления сортировкой результатов.
- profile.runctx(command, globals, locals, filename=None, sort=-1)¶
Эта функция похожа на
run(), но с дополнительными аргументами для указания словарей глобальных и локальных переменных для строки команда. Эта процедура выполняет:exec(command, globals, locals)
и собирает статистику профилирования, как в функции
run()выше.
- class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)¶
Этот класс обычно используется только тогда, когда требуется более точный контроль профилирования, чем предоставляет функция
profiling.tracing.run().Можно указать собственный таймер для измерения времени выполнения кода с помощью аргумента timer. Это должна быть функция, возвращающая одно число, представляющее текущее время. Если число целое, timeunit задаёт множитель, определяющий длительность каждой единицы времени. Например, если таймер возвращает время, измеряемое в тысячных долях секунды, единица времени будет
.001.Непосредственное использование класса
Profileпозволяет форматировать результаты профилирования без записи данных профиля в файл:import profiling.tracing import pstats import io from pstats import SortKey pr = profiling.tracing.Profile() pr.enable() # ... сделать что-то ... pr.disable() s = io.StringIO() sortby = SortKey.CUMULATIVE ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() print(s.getvalue())
Класс
Profileтакже можно использовать как контекстный менеджер (поддерживается только вprofiling.tracing, но не в устаревшем модулеprofile; см. Типы контекстных менеджеров):import profiling.tracing with profiling.tracing.Profile() as pr: # ... сделать что-то ... pr.print_stats()
Изменено в версии 3.8: Добавлена поддержка контекстного менеджера.
- enable()¶
Начать сбор данных профилирования. Только в
profiling.tracing.
- disable()¶
Остановить сбор данных профилирования. Только в
profiling.tracing.
- create_stats()¶
Остановить сбор данных профилирования и сохранить результаты внутри как текущий профиль.
- print_stats(sort=-1)¶
Создать объект
Statsна основе текущего профиля и вывести результаты в stdout.Параметр sort задаёт порядок сортировки отображаемой статистики. Он принимает один ключ или кортеж ключей для многоуровневой сортировки, как в
pstats.Stats.sort_stats().Добавлено в версии 3.13:
print_stats()теперь принимает кортеж ключей.
- dump_stats(filename)¶
Записать результаты текущего профиля в filename.
- runctx(cmd, globals, locals)¶
Профилировать cmd с помощью
exec()с указанным глобальным и локальным окружением.
- runcall(func, /, *args, **kwargs)¶
Профилировать
func(*args, **kwargs)
Обратите внимание, что профилирование будет работать, только если вызванная команда/функция действительно
возвращает управление. Если интерпретатор завершается (например, вызовом sys.exit()
во время выполнения вызванной команды/функции), результаты профилирования не будут
выведены.
Отличия от profiling.tracing¶Differences from profiling.tracing
Модуль profile отличается от profiling.tracing в нескольких
аспектах:
Бо́льшие накладные расходы. Реализация на чистом Python существенно медленнее C-реализации, поэтому она не подходит для профилирования долго работающих программ или кода, чувствительного к производительности.
Поддержка калибровки. Модуль profile поддерживает калибровку для компенсации накладных расходов профилирования. В profiling.tracing она не нужна, потому что C-реализация даёт пренебрежимо малые накладные расходы.
Пользовательские таймеры. Оба модуля поддерживают пользовательские таймеры, но profile принимает функции-таймеры, возвращающие кортежи (например, os.times()), а profiling.tracing требует функцию, возвращающую одно число.
Создание подклассов. Чистая реализация на Python упрощает создание подклассов и расширение для настройки поведения профилирования.
Что такое детерминированное профилирование?¶What is deterministic profiling?
Детерминированное профилирование означает, что отслеживаются все события вызова
функции, возврата из функции и исключения, и выполняются точные
замеры времени для интервалов между этими событиями (в течение которых выполняется пользовательский код). В отличие от этого, статистическое профилирование (которое предоставляется модулем profiling.sampling) периодически сэмплирует актуальный указатель инструкций и выводит предположения о том, где тратится время. Последний метод традиционно имеет меньшие накладные расходы (поскольку код не требует инструментирования), но даёт лишь относительные указания на то, где расходуется время.
В Python, поскольку во время выполнения активен интерпретатор, для детерминированного профилирования не требуется инструментированный код. Python автоматически предоставляет ловушку (хук) (необязательный колбэк) для каждого события. Кроме того, интерпретируемый характер Python добавляет столько накладных расходов на выполнение, что детерминированное профилирование в типичных приложениях добавляет лишь небольшие вычислительные накладные расходы. В результате детерминированное профилирование не так затратно, но при этом предоставляет обширную статистику времени выполнения программы Python.
Статистику количества вызовов можно использовать для выявления ошибок в коде (неожиданные количества) и для определения возможных точек встраивания (высокое количество вызовов). Статистику внутреннего времени можно использовать для выявления «горячих циклов», которые следует тщательно оптимизировать. Статистику суммарного времени следует использовать для выявления высокоуровневых ошибок в выборе алгоритмов. Обратите внимание, что нестандартная обработка суммарного времени в этом профилировщике позволяет напрямую сравнивать статистику рекурсивных реализаций алгоритмов с итеративными.
Ограничения¶Limitations
Одно ограничение связано с точностью информации о времени. Существует фундаментальная проблема с детерминированными профилировщиками, касающаяся точности. Наиболее очевидное ограничение состоит в том, что внутренние «часы» тикают с частотой (обычно) около 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, чем для модуля с меньшими накладными расходами profiling.tracing. По этой причине profile предоставляет возможность калибровки для конкретной платформы, чтобы эту ошибку можно было вероятностно (в среднем) устранить. После калибровки профилировщик станет точнее (в смысле наименьших квадратов), но иногда будет выдавать отрицательные числа (когда количество вызовов исключительно мало, и боги вероятности не на вашей стороне :-)). Не пугайтесь отрицательных чисел в профиле. Они должны только появляться, если вы откалибровали профилировщик, и результаты на самом деле лучше, чем без калибровки.
Калибровка¶Calibration
Профилировщик модуля profile вычитает константу из времени обработки каждого события, чтобы компенсировать накладные расходы на вызов функции времени и сохранение результатов. По умолчанию константа равна 0. Следующую процедуру можно использовать для получения лучшей константы для данной платформы (см. Ограничения).
import profile
pr = profile.Profile()
for i in range(5):
print(pr.calibrate(10000))
Метод выполняет количество вызовов Python, заданное аргументом, напрямую и снова под профилировщиком, измеряя время для обоих случаев. Затем он вычисляет скрытые накладные расходы на одно событие профилировщика и возвращает это значение в виде числа с плавающей запятой. Например, на процессоре Intel Core i5 с частотой 1,8 ГГц под управлением macOS и с использованием time.process_time() в Python в качестве таймера магическое число составляет около 4,04e-6.
Цель этого упражнения – получить достаточно стабильный результат. Если ваш компьютер очень быстрый или функция таймера имеет низкое разрешение, возможно, придётся передать 100000 или даже 1000000, чтобы получить стабильные результаты.
Когда получен стабильный результат, его можно использовать тремя способами:
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)
Если есть выбор, лучше выбрать меньшую константу – тогда результаты будут «реже» отображаться как отрицательные в статистике профилирования.
Использование пользовательского таймера¶Using a custom timer
Если требуется изменить способ определения текущего времени (например, принудительно использовать wall-clock time или время процесса), передайте нужную функцию таймера в конструктор класса Profile:
pr = profile.Profile(your_time_func)
Полученный профилировщик будет вызывать your_time_func. В зависимости от того, используется ли profile.Profile или profiling.tracing.Profile,
возвращаемое значение your_time_func будет интерпретироваться по-разному:
profile.Profileyour_time_funcдолжна возвращать одно число или список чисел, сумма которых равна текущему времени (какos.times()). Если функция возвращает одно число времени или длина возвращаемого списка равна 2, то будет использована особенно быстрая версия диспетчерской подпрограммы.Следует помнить, что необходимо откалибровать класс профилировщика для выбранной функции таймера (см. Калибровка). Для большинства машин таймер, возвращающий одно целое число, даёт наилучшие результаты с точки зрения низких накладных расходов при профилировании. (
os.times()довольно плох, так как возвращает кортеж чисел с плавающей запятой). Если требуется заменить таймер на более качественный наиболее чистым способом, создайте производный класс и жёстко задайте замещающий метод диспетчеризации, который наилучшим образом обрабатывает вызов таймера, вместе с соответствующим калибровочным коэффициентом.profiling.tracing.Profileyour_time_funcдолжна возвращать одно число. Если она возвращает целые числа, можно также вызвать конструктор класса со вторым аргументом, указывающим реальную длительность одной единицы времени. Например, еслиyour_integer_time_funcвозвращает время, измеряемое в тысячах секунд, экземплярProfileконструируется следующим образом:pr = profiling.tracing.Profile(your_integer_time_func, 0.001)
Поскольку класс
profiling.tracing.Profileне может быть откалиброван, пользовательские функции таймера следует использовать с осторожностью и делать их как можно более быстрыми. Для достижения наилучших результатов с пользовательским таймером может потребоваться жёстко прописать его в коде C внутреннего модуля_lsprof.
Python 3.3 добавляет несколько новых функций в time, которые можно использовать для точных измерений процессорного или реального времени. Например, см. time.perf_counter().
См. также
profilingОбзор инструментов профилирования Python.
profiling.tracingРекомендуемая замена для этого модуля.
pstatsСтатистический анализ и форматирование данных профиля.