Профилирование в Python: кто вызвал функцию? - PullRequest
48 голосов
/ 09 мая 2009

Я профилирую в Python, используя cProfile. Я нашел функцию, которая занимает много процессорного времени. Как узнать, какая функция вызывает эту тяжелую функцию чаще всего?

EDIT:

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

Ответы [ 8 ]

105 голосов
/ 09 мая 2009

Я почти всегда просматриваю вывод модуля cProfile, используя Gprof2dot , в основном он конвертирует вывод в граф graphvis (файл .dot), например:

example gprof2dot output

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

Использование:

python -m cProfile -o output.pstats path/to/your/script arg1 arg2
gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png
34 голосов
/ 09 мая 2009

Это может не ответить на ваш вопрос напрямую, но определенно поможет. Если использовать профилировщик с опцией --sort cumulative, он отсортирует функции по кумулятивному времени. Что полезно для обнаружения не только тяжелых функций, но и функций, которые их вызывают.

python -m cProfile --sort cumulative myScript.py

Существует обходной путь для получения функции вызывающего абонента:

import inspect
print inspect.getframeinfo(inspect.currentframe().f_back)[2]

Вы можете добавить столько f_back, сколько захотите, на случай, если вы захотите звонить вызывающему абоненту Если вы хотите рассчитать частые звонки, вы можете сделать это:

record = {}

caller = inspect.getframeinfo(inspect.currentframe().f_back)[2]
record[caller] = record.get(caller, 0) + 1

Затем распечатайте их по порядку частоты:

print sorted(record.items(), key=lambda a: a[1])
10 голосов
/ 09 мая 2009

inspect.stack () выдаст вам текущий стек вызовов.

3 голосов
/ 10 мая 2009

Возможно, вы захотите взглянуть на pycallgraph .

2 голосов
/ 16 октября 2015

Это можно сделать с помощью профилировщика cProfile в стандартной библиотеке.
В pstats.Stats (результат профилировщика) есть метод print_callees (или альтернативно print_callers).


Пример кода:

import cProfile, pstats
pr = cProfile.Profile()
pr.enable()

# ... do something ...

pr.disable()
ps = pstats.Stats(pr).strip_dirs().sort_stats('cumulative')
ps.print_callees()

Результат будет примерно таким:

Function                           called...
                                       ncalls  tottime  cumtime
ElementTree.py:1517(_start_list)   ->   24093    0.048    0.124  ElementTree.py:1399(start)
                                        46429    0.015    0.041  ElementTree.py:1490(_fixtext)
                                        70522    0.015    0.015  ElementTree.py:1497(_fixname)
ElementTree.py:1527(_data)         ->   47827    0.017    0.026  ElementTree.py:1388(data)
                                        47827    0.018    0.053  ElementTree.py:1490(_fixtext)

Слева у вас есть вызывающий, справа у вас есть вызываемый.
(например, _fixtext был вызван _data 47827 раз и _start_list 46429 раз)


Смотри также:


Пара заметок:

  • Ваш код должен быть отредактирован для этого (вставьте эти операторы профиля).
    (то есть невозможно использовать из командной строки, например python -m cProfile myscript.py. Хотя для этого можно написать отдельный скрипт)
  • Немного не связано, но strip_dirs() должно идти до sort_stats() (иначе сортировка не работает)
1 голос
/ 09 мая 2009

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

0 голосов
/ 21 августа 2013

Pycscope делает это. Я только что нашел его сегодня, поэтому не могу сказать, насколько он хорош, но несколько примеров, которые я попробовал, были довольно хорошими (хотя и не идеальными).

https://pypi.python.org/pypi/pycscope/

Вы могли бы использовать это для генерации файла cscope, а затем плагина cscope из редактора, в частности VIM. Я пытался использовать его с ванильным cscope, кажется, что простой cscope запутался.

0 голосов
/ 10 мая 2009

Извините, я не знаком с Python, но есть общий метод , который работает, если вы можете вручную прервать выполнение в случайное время.

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

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

ПРИМЕЧАНИЕ. Этот процесс больше похож на диагностику, чем на измерение. Предположим, что плохой звонок тратит 90% времени. Затем, каждый раз, когда вы его останавливаете, вероятность того, что неправильный оператор вызова находится прямо в стеке вызовов, составляет 90%, и вы сможете увидеть, что он плохой. Однако, если вы хотите точно измерить потери, это другая проблема. Для этого вам понадобится намного больше сэмплов, чтобы увидеть, какой% из них содержит этот вызов. Или, в качестве альтернативы, просто исправьте виновный вызов, отметьте ускорение, и это точно скажет вам, какой была потеря.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...