Профилировщики Python¶
Исходный код: Lib/profile.py и Lib/pstats.py
Введение в профилировщики¶
single: deterministic profiling single: profiling, deterministic
cProfile
и profile
обеспечивают
детерминированное профилирование <deterministic profiling> программ Python. profile
- ряд статистики, которая описывает как часто и как долго различные части
выполненной программы. Эти статистические данные могут быть отформатированы в
отчеты через модуль pstats
.
Стандартная библиотека Python предоставляет две различные реализации одного и того же интерфейса профилирования:
cProfile
рекомендуется большинству пользователей; это расширение C с разумными накладными расходами, которое делает его подходящим для профилирования продолжительных программ. Основана наlsprof
, внесена Brett Rosen и Ted Czotter.profile
, чисто Python модуль, интерфейс которого имитируетсяcProfile
, но который добавляет значительные накладные расходы к профилированным программам. Если вы пытаетесь расширить профилировщик каким-то образом, задача может быть проще с этим модулем. Первоначально разработан и написан Jim Roskind.
Примечание
Модули профилировщика разработаны, чтобы обеспечить профиль выполнения для
данной программы, не для сопоставительного анализа целей (для который, есть
timeit
для довольно точных результатов). Это особенно касается бенчмаркинга
Python кода в сравнении с C кодом: профайлеры вводят накладные
расходы для Python код, но не для функций C-уровня, и поэтому C
код будет казаться более быстрым, чем любой Python.
Мгновенное руководство пользователя¶
Этот раздел предназначен для пользователей, которые «не хотят читать руководство». Он предоставляет очень краткий обзор и позволяет пользователю быстро выполнять профилирование существующего приложения.
Чтобы профилировать функцию, которая принимает один аргумент, можно сделать:
import cProfile
import re
cProfile.run('re.compile("foo|bar")')
(Используйте profile
вместо cProfile
, если последняя недоступна в
системе.)
Вышеуказанное действие приведет к запуску re.compile()
и печати результатов
профиля, как показано ниже:
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
были primitive, что означает, что вызов не был вызван рекурсией. Следующая
строка: Ordered by: standard name
указывает, что строка текста в правом столбце был
используемый для сортировки выходных данных. Заголовки столбцов включают:
- ncalls
- для количества вызовов.
- tottime
- для общего времени, затраченного в данной функции (и исключая время, выполненное в вызовах подфункций)
- percall
- частное от
tottime
, деленное наncalls
- cumtime
- совокупное время, проведенное в этой и всех подфункциях (от вызова до выхода). Это число - точный even для рекурсивных функций.
- percall
- частное от
cumtime
, разделенное примитивными призывами - filename:lineno(function)
- предоставляет соответствующие данные каждой функции
При наличии двух чисел в первом столбце (например, 3/1
) функция
рекурсивна. Второй значение - это количество примитивных вызовов, а первый -
общее количество вызовов. Обратите внимание, что, когда функция не повторно
проклинает, эти два значения - то же, и только единственное число напечатано.
Вместо печати выходных данных в конце прогона профиля можно сохранить результаты
в файле, указав имя файла для функции run()
:
import cProfile
import re
cProfile.run('re.compile("foo|bar")', 'restats')
Класс pstats.Stats
считывает результаты профиля из файла и форматирует их
различными способами.
Файлы cProfile
и profile
также могут быть вызваны как сценарий для
профилирования другого сценария. Например:
python -m cProfile [-o output_file] [-s sort_order] (-m module | myscript.py)
-o
записывает результаты профиля в файл, а не в stdout
-s
указывает один из sort_stats()
sort значения для сортировки
выходных данных. Это применимо только в том случае, если параметр -o
не
указан.
-m
указывает, что вместо сценария выполняется профилирование модуля.
У класса модуля pstats
Stats
есть множество методов для управления, и
печать данных, сохраненных в профиль, заканчивается файл:
import pstats
from pstats import SortKey
p = pstats.Stats('restats')
p.strip_dirs().sort_stats(-1).print_stats()
Метод strip_dirs()
удалил внешний путь из всех имен модулей. Метод sort_stats()
отсортировал все записи по напечатанному стандартному модулю/строке/имени
строка. Метод print_stats()
распечатал всю статистику. Можно попробовать
выполнить следующую сортировку вызовов:
p.sort_stats(SortKey.NAME)
p.print_stats()
Первый вызов фактически сортирует список по имени функции, а второй вызов распечатывает статистику. Ниже приведены некоторые интересные призывы экспериментировать с:
p.sort_stats(SortKey.CUMULATIVE).print_stats(10)
При этом профиль сортируется по кумулятивному времени в функции, а затем печатаются только десять наиболее значимых строк. Если вы хотите понять, какие алгоритмы требуют времени, то приведенная выше строка будет использоваться.
Если бы вы искали, какие функции много петляют, и занимали бы много времени, вы бы сделали:
p.sort_stats(SortKey.TIME).print_stats(10)
для сортировки по времени, потраченному внутри каждой функции, а затем распечатать статистику для десяти основных функций.
Вы также можете попробовать:
p.sort_stats(SortKey.FILENAME).print_stats('__init__')
При этом вся статистика будет отсортирована по имени файла, а затем будет
распечатана статистика только для методов класса init (так как они написаны с
помощью __init__
в них). В качестве последнего примера можно попробовать:
p.sort_stats(SortKey.TIME, SortKey.CUMULATIVE).print_stats(.5, 'init')
Эта строка сортирует статистику с первичным ключом времени и вторичным ключом
кумулятивного времени, а затем распечатывает часть статистики. Если говорить
конкретно, список сначала отсеивается до 50% (re: .5
) от его
первоначального размера, затем поддерживаются только строки, содержащие
init
, и этот подсписок печатается.
Если вы задались вопросом о том, какие функции называются вышеуказанными
функциями, вы можете сделать это сейчас (p
по-прежнему сортируется по
последним критериям):
p.print_callers(.5, 'init')
и вы получите список вызывающих абонентов для каждой из перечисленных функций.
Если вам нужны дополнительные функциональные возможности, вам придется прочитать руководство или угадать, что делают следующие функции:
p.print_callees()
p.add('restats')
Вызванный в качестве сценария модуль pstats
представляет собой браузер
статистики для чтения и проверки дампов профиля. Он имеет простой линейно-
ориентированный интерфейс (реализованный с помощью cmd
) и интерактивную
справку.
profile
и cProfile
справочник модуля¶
Модули profile
и cProfile
обеспечивают следующие функции:
-
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()
, с добавленными аргументами, чтобы предоставить глобалы и местные словари для command строка. Эта подпрограмма выполняется:exec(command, globals, locals)
и собирает профильную статистику как в функции
run()
выше.
-
class
profile.
Profile
(timer=None, timeunit=0.0, subcalls=True, builtins=True)¶ Этот класс обычно является используемый только в том случае, если требуется более точный контроль за профилированием, чем то, что обеспечивает функция
cProfile.run()
.Пользовательский таймер может быть предоставлен для измерения, сколько времени занимает код для запуска через аргумент timer. Это должна быть функция, которая возвращает одно число представляя текущее время. Если число является целым числом, timeunit определяет множитель, который указывает продолжительность каждой единицы времени. Например, если таймер возвращает время, измеренное в тысячах секунд, единица времени будет
.001
.Непосредственно использование класса
Profile
позволяет форматировать результаты профиля, не в письме к данные о профиле файлу:import cProfile, pstats, io from pstats import SortKey pr = cProfile.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
может также быть используемый как менеджером контекст (поддержанный только в модулеcProfile
. см. Типы менеджера контекста):import cProfile with cProfile.Profile() as pr: # ... делать что-то ... pr.print_stats()
Изменено в версии 3.8: Добавлена поддержка диспетчера контекст.
-
create_stats
()¶ Прекратить сбор данных профилирования и записать результаты в качестве текущего профиля.
-
print_stats
(sort=-1)¶ Создайть объект
Stats
на основе текущего профиля и распечатайте результаты для stdout.
-
dump_stats
(filename)¶ Записать результаты текущего профиля в filename.
-
runctx
(cmd, globals, locals)¶ Выполнить профилирование cmd через
exec()
с помощью указанной глобальной и локальная среды.
-
runcall
(func, *args, **kwargs)¶ Профиль
func(*args, **kwargs)
-
Следует отметить, что профилирование будет работать только в том случае, если
вызываемая команда/функция действительно возвращает. Если интерпретатор
завершается (например, посредством вызова sys.exit()
во время выполнения
вызываемой команды/функции), результаты профилирования не будут напечатаны.
Класс Stats
¶
Анализ данных профилировщика выполняется с использованием класса Stats
.
-
class
pstats.
Stats
(*filenames or profile, stream=sys.stdout)¶ Конструктор класса создает сущность «объекта статистики» от filename (или список имен файлов) или от
Profile
сущность. Выходные данные будут напечатаны в потоке, указанном параметром stream.Файл, выбранный вышеуказанным конструктором, должен быть создан соответствующей версией
profile
илиcProfile
. Чтобы быть конкретной, есть совместимость файла no, гарантируемая с будущими версиями этого профилировщика, и нет никакой совместимости с файлами, созданными другими профилировщиками, или тот же профилировщик работает на другой операционной системе. Если представлено несколько файлов, то все статистические данные по идентичным функциям будут объединены, так что общий обзор нескольких процессов может быть рассмотрен в одном отчете. Если дополнительные файлы должны быть объединены с данными в существующем объектеStats
, методadd()
может быть используемый.Вместо того, чтобы читать данные о профиле из файла, объект
cProfile.Profile
илиprofile.Profile
может быть используемый как источником данных профиля.Объекты
Stats
имеют следующие методы:-
strip_dirs
()¶ Этот метод для класса
Stats
удаляет всю ведущую информацию о пути из имен файлов. Это очень полезно для уменьшения размера распечатки, чтобы поместиться в (близко к) 80 столбцам. Этот метод изменяет объект, и лишенная информация теряется. После выполнения операции полосы объект считается имеющим свои записи в «случайном» порядке, как это было сразу после инициализации и загрузки объекта. Еслиstrip_dirs()
приводит к неразличимости двух имен функций (они находятся в одной строке с одним и тем же именем файла и имеют одно и то же имя функции), то статистика для этих двух записей накапливается в одну запись.
-
add
(*filenames)¶ Этот метод класса
Stats
накапливает дополнительную профильную информацию в текущий профильный объект. Его аргументы должны ссылаться на имена файлов, созданные соответствующей версиейprofile.run()
илиcProfile.run()
. Статистика для идентично именованных функций (re: file, line, name) автоматически накапливается в статистику одной функции.
-
dump_stats
(filename)¶ Сохраните данные, загруженные в объект
Stats
, в файл с именем filename. Файл создается, если он не существует, и перезаписывается, если он уже существует. Это эквивалентно одноименному методу в классахprofile.Profile
иcProfile.Profile
.
-
sort_stats
(*keys)¶ Этот метод изменяет объект
Stats
путем его сортировки в соответствии с предоставленными критериями. Аргумент может быть или строка или SortKey enum, определяющим в некотором роде основание (пример:'time'
,'name'
,SortKey.TIME
илиSortKey.NAME
). Аргумент SortKey enums имеет преимущество перед аргументом строка в том, что он более надежен и менее подвержен ошибкам.Когда больше чем один ключ обеспечен, тогда дополнительные ключи - используемый как вторичные критерии, когда есть равенство во всех ключах, отобранных перед ними. Например,
sort_stats(SortKey.NAME, SortKey.FILE)
будет сортировать все записи в соответствии с их именем функции и разрешить все связи (идентичные имена функций) путем сортировки по имени файла.Для аргумента строка аббревиатуры могут быть используемый для любых ключевых имен, пока аббревиатура однозначна.
Ниже приведены допустимые строка и SortKey:
Правильная строка Arg Правильный enum Arg Смысл 'calls'
SortKey.CALLS количество вызовов 'cumulative'
SortKey.CUMULATIVE совокупное время 'cumtime'
N/A совокупное время 'file'
N/A имя файла 'filename'
SortKey.FILENAME имя файла 'module'
N/A имя файла 'ncalls'
N/A количество вызовов 'pcalls'
SortKey.PCALLS примитивный счетчик вызовов 'line'
SortKey.LINE номер строки 'name'
SortKey.NAME имя функции 'nfl'
SortKey.NFL имя/файл/строка 'stdname'
SortKey.STDNAME стандартное имя 'time'
SortKey.TIME внутреннее время 'tottime'
N/A внутреннее время Следует отметить, что все виды статистики находятся в порядке убывания (при этом наиболее трудоемкие элементы помещаются в первую очередь), где поиск по имени, файлу и номеру строки выполняется в порядке возрастания (алфавитном). Тонкое различие между
SortKey.NFL
иSortKey.STDNAME
заключается в том, что стандартное имя является видом печатаемого названия, что означает, что номера встроенных строк сравниваются нечетным образом. Например, строки 3, 20 и 40 (если имена файлов одинаковы) будут отображаться в порядке 20, 3 и 40 строка. В отличие от этого,SortKey.NFL
выполняет числовое сравнение номеров строк. На самом деле,sort_stats(SortKey.NFL)
то же самое, что иsort_stats(SortKey.NAME, SortKey.FILENAME, SortKey.LINE)
.По причинам обратной совместимости разрешены числовые аргументы
-1
,0
,1
и2
. Они интерпретируются как'stdname'
,'calls'
,'time'
и'cumulative'
соответственно. Если этот старый формат стиля (числовой) имеет значение используемый, будет используемый только один ключ сортировки (числовой ключ), и дополнительные аргументы будут игнорироваться молча.Добавлено в версии 3.7: Добавлен перечисление SortKey.
-
reverse_order
()¶ Этот метод для класса
Stats
изменяет порядок основного списка в объекте. Обратите внимание, что по умолчанию по возрастанию и убыванию выбирается правильно на основе выбранного ключа сортировки.
-
print_stats
(*restrictions)¶ Метод для класса
Stats
выводит отчет, как описано в определенииprofile.run()
.Заказ печати на основе последней операции
sort_stats()
, сделанной на объекте (подвергающийся протестам вadd()
иstrip_dirs()
).Обеспеченными аргументами (если таковые имеются) может быть используемый, чтобы ограничить список вниз значительными записями. Первоначально список принимается за полный набор профилированных функций. Каждое ограничение является либо целым числом (для выбора числа строк), либо десятичной дробью в диапазоне от 0,0 до 1,0 включительно (для выбора процента строк), либо строка, которая будет интерпретироваться как регулярное выражение (для сопоставления со стандартным именем, которое печатается). Если предусмотрено несколько ограничений, то они применяются последовательно. Например:
print_stats(.1, 'foo:')
сначала ограничит печать первыми 10% списка, а затем только функциями печати, которые были частью имени файла
.*foo:
. В отличие от команды:print_stats('foo:', .1)
ограничит список всеми функциями, имеющими имена файлов
.*foo:
, а затем начнет печатать только первые 10% из них.
-
print_callers
(*restrictions)¶ Этот метод для класса
Stats
печатает список всех функций, которые вызвали каждую функцию в представленной базе данных. Порядок идентичен порядку, предоставленномуprint_stats()
, и определение ограничительного аргумента также идентично. О каждом вызывающем абоненте сообщается по собственной линии. Формат немного отличается в зависимости от профилировщика, который производил статистику:- При использовании параметра
profile
после каждого вызывающего абонента в скобках отображается номер, показывающий, сколько раз был выполнен этот конкретный вызов. Для удобства второе не заключенное в скобки число повторяет совокупное время, проведенное в функции справа. - При использовании команды
cProfile
каждому вызывающему абоненту предшествуют три номера: количество выполненных вызовов, общее и совокупное время, затраченное в текущей функции при вызове этой конкретной вызывающей функции.
- При использовании параметра
-
print_callees
(*restrictions)¶ Этот метод для класса
Stats
печатает список всех функций, которые были вызваны указанной функцией. Помимо этого разворота направления вызовов (re: called vs был вызван), аргументы и порядок идентичны методуprint_callers()
.
-
Что такое детерминированное профилирование?¶
детерминированное профилирование <deterministic profiling> предназначен для отражения того факта, что все события вызов функции, возврат функции и исключение отслеживаются, и для интервалов между этими событиями (в течение этого времени выполняется код пользователя) выполняются точные временные интервалы. В отличие от этого, статистическое профилирование <statistical profiling> (что не выполняется этим модулем) случайным образом дискретизирует эффективный указатель команды и выводит, где уходит время. Последний метод традиционно включает в себя меньше накладных расходов (так как код не нуждается в инструментарии), но обеспечивает только относительные указания того, где уходит время.
В Python с тех пор есть активное интерпретатор во время выполнения, присутствие инструментованного код не требуется, чтобы сделать детерминированное профилирование. Python автоматически предоставляет hook (необязательный колбэк) для каждого события. Кроме того, интерпретируемая природа Python имеет тенденцию добавлять к выполнению так много служебных издержек, что детерминированное профилирование имеет тенденцию только добавлять небольшие накладные расходы на обработку в типичных приложениях. В результате детерминированное профилирование не является столь дорогостоящим, но обеспечивает обширную статистику времени выполнения программы Python.
Вызов счетчиков статистики может быть используемый, чтобы определить ошибки в код (удивляющий количество), и определить возможные точки действующего расширения (высоко называют количество). Внутренняя статистика времени может быть используемый, чтобы определить «горячие петли», которые должны быть внимательно оптимизированы. Статистика совокупного времени должна быть используемый, чтобы определить ошибки высокого уровня в выборе алгоритмов. Заметим, что необычная обработка кумулятивного времени в этом профилировщике позволяет напрямую сравнивать статистику рекурсивных реализаций алгоритмов с итеративными реализациями.
Ограничения¶
Одно ограничение связано с точностью информации синхронизации. Существует фундаментальная проблема детерминированных профилировщиков, связанная с точностью. Наиболее очевидным ограничением является то, что нижележащие «часы» тикнут только со скоростью (обычно) около .001 секунды. Следовательно, никакие измерения не будут более точными, чем базовый тактовый сигнал. Если будет проведено достаточно измерений, то «ошибка» будет иметь тенденцию к усреднению. К сожалению, удаление этой первой ошибки вызывает второй источник ошибки.
Вторая проблема состоит в том, что это «требует времени» от того, когда событие послано до требования профилировщика получить время на самом деле получает состояние часов. Аналогично, существует определенное отставание при выходе из события профилировщика обработчик от времени, когда было получено (и затем отброшено) значение часов, до тех пор, пока код пользователя снова не будет выполняться. В результате функции, которые вызываются много раз или вызывают много функций, обычно накапливают эту ошибку. Ошибка, которая накапливается таким образом, обычно меньше, чем точность часов (менее одного такта), но она может накапливаться и становиться очень значительной.
Проблема более важна с profile
, чем с более низко-верхним cProfile
. По
этой причине profile
предоставляет средство калибровки для данной платформы,
так что эта ошибка может быть устранена вероятностно (в среднем). После
калибровки профилировщика он будет более точным (в наименьшем квадратном
смысле), но иногда будет производить отрицательные числа (когда подсчёты вызовов
исключительно низки, а боги вероятности работают против вас: -).) сделайте
не быть встревоженными отрицательными числами в профиле. Они должны
только появляться, если вы калибровали своего профилировщика, и результаты
на самом деле лучше, чем без калибровки.
Ограничения¶
Профилировщик модуля profile
вычитает константу из времени обработки каждого
события, чтобы компенсировать накладные расходы на вызов временной функции и
отключение результатов. По умолчанию константа равна 0. Следующая процедура
может быть используемый для получения лучшей константы для данной платформы (см.
Ограничения):
import profile
pr = profile.Profile()
for i in range(5):
print(pr.calibrate(10000))
Метод выполняет количество вызовов Python, заданных аргументом, непосредственно и снова под профилировщиком, измеряя время для обоих. Затем он вычисляет скрытые накладные расходы на событие профилировщика и возвращает это как float. Например, на 1.8 Ghz Intel Core i5, управляющем Mac OS X и использующем время Python’а time.process_time() как таймер, волшебное число о 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)
Если у вас есть выбор, вам лучше выбрать меньшую константу, и тогда ваши результаты будут «реже» проявляться как отрицательные в профильной статистике.
Использование пользовательского таймера¶
Если необходимо изменить способ определения текущего времени (например,
принудительно использовать время настенных часов или истекшее время процесса),
передайте нужную функцию синхронизации конструктору класса Profile
:
pr = profile.Profile(your_time_func)
Полученный профилировщик вызовет your_time_func
. В зависимости от того, используется
ли profile.Profile
или cProfile.Profile
, возвращает значение your_time_func
будет
интерпретироваться по-разному:
profile.Profile
your_time_func
должен возвращает единственное число или список чисел, сумма которых - текущее время (как какойos.times()
возвращает). Если функция возвращает один временной номер или список номеров возвращенный имеет длину 2, то вы получите особенно быструю версию подпрограммы отправки.Обратите внимание на необходимость калибровки класса профилировщика для выбранной функции таймера (см. Ограничения). Для большинства машин, таймера, что возвращает одинокий целочисленный значение обеспечит лучшие результаты с точки зрения низкого наверху во время профилирования. (
os.times()
- pretty плохо как он возвращает кортеж плавающей запятой значения). Если вы хотите заменить лучший таймер самым чистым способом, выведите класс и выполните аппаратную проводку метода замены, который лучше всего обрабатывает ваш вызов таймера, вместе с соответствующей калибровочной константой.cProfile.Profile
your_time_func
должен возвращает один номер. Если это целые числа возвращает, вы можете также призвать конструктора класса со вторым аргументом, определяющим реальную продолжительность одной единицы времени. Например, еслиyour_integer_time_func
возвращает раз измерено в тысячах секунд, вы построитеProfile
сущность следующим образом:pr = cProfile.Profile(your_integer_time_func, 0.001)
Поскольку класс
cProfile.Profile
не может быть откалиброван, пользовательские функции таймера должны быть используемый с осторожностью и должны быть как можно быстрее. Для лучших результатов с пользовательским таймером это могло бы быть необходимо для твердого-код это в источнике C внутреннего модуля_lsprof
.
Python 3.3 добавляет несколько новых функций в time
, которые могут
быть используемый для точного измерения технологического или настенного времени.
Например, см. раздел time.perf_counter()
.