Профилирование в Python: кто вызвал функцию?
Я профилирую Python, используя cProfile
. Я нашел функцию, которая занимает много процессорного времени. Как узнать, какая функция больше всего называет эту тяжелую функцию?
EDIT:
Я соглашусь на обходной способ: могу ли я написать строку Python внутри этой тяжелой функции, которая будет печатать имя вызываемой функции?
Ответы
Ответ 1
Это может не ответить на ваш вопрос напрямую, но определенно поможет. Если использовать профилировщик с опцией --sort кумулятивный, он будет сортировать функции по кумулятивному времени. Это полезно для обнаружения не только тяжелых функций, но и функций, которые их называют.
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])
Ответ 2
Я почти всегда просматриваю вывод модуля cProfile с помощью Gprof2dot, в основном он преобразует вывод в график графика (a .dot
файл), например:
![example gprof2dot output]()
Это очень легко определить, какая функция самая медленная, и какую функцию [s] называют ее.
Использование:
python -m cProfile -o output.pstats path/to/your/script arg1 arg2
gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png
Ответ 3
inspect.stack() даст вам текущий стек вызывающего абонента.
Ответ 4
Возможно, вы захотите взглянуть на pycallgraph.
Ответ 5
Я сам не использовал cProfile, но большинство профилировщиков предоставляют вам иерархию вызовов.
Googling Я нашел этот слайды о cProfile. Может быть, это помогает. Page 6 выглядит так: cProfile предоставляет иерархию.
Ответ 6
Извините, я не знаком с Python, но там общий метод работает, если вы можете вручную прерывать выполнение в произвольное время.
Просто сделайте это и отобразите стек вызовов. Он расскажет вам, с большой вероятностью, то, что вы хотите знать. Если вы хотите быть более уверенным, просто делайте это несколько раз.
Это работает, потому что виновный вызывающий должен находиться в стеке вызовов за долю времени, потраченного впустую, что в течение этого времени предоставляет его прерываниям, распространяется ли оно на многие короткие вызовы или на несколько длинных из них.
ПРИМЕЧАНИЕ. Этот процесс больше похож на диагностику, чем на измерение. Предположим, что плохой вызов тратит 90% времени. Затем каждый раз, когда вы его останавливаете, вероятность 90% заключается в том, что оператор плохого вызова находится прямо в стеке вызовов, чтобы вы могли видеть, и вы увидите, что это плохо. Однако, если вы хотите точно измерить потерю, это другая проблема. Для этого вам понадобится намного больше образцов, чтобы узнать, в каком% из них содержится этот вызов. Или, альтернативно, просто исправьте виновный звонок, отметьте ускорение, и это точно скажет вам, что произошло.
Ответ 7
Pycscope делает это. Я просто нашел его сегодня, поэтому я не могу говорить о том, насколько он хорош, но несколько примеров, которые я пробовал, были довольно хорошими (хотя и не идеальными).
https://pypi.python.org/pypi/pycscope/
Вы использовали бы это для создания файла cscope, а затем плагина cscope из редактора VIM. Я попытался использовать его с ванильным cscope, кажется, что простой cscope запутался.
Ответ 8
Это можно сделать с помощью профайлера 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
. Хотя для этого можно написать отдельный script)
- Немного несвязанный, но
strip_dirs()
должен идти до sort_stats()
(иначе сортировка не работает)