Как я могу профилировать код Python построчно?
Я использовал cProfile для профилирования своего кода, и он работал отлично. Я также использую gprof2dot.py, чтобы визуализировать результаты (делает это немного яснее).
Тем не менее, cProfile (и большинство других профилировщиков Python, которые я видел до сих пор), кажется, только профилирует на уровне вызова функции. Это вызывает путаницу, когда определенные функции вызываются из разных мест - я понятия не имею, занимает ли вызов № 1 или вызов № 2 большую часть времени. Это становится еще хуже, когда рассматриваемая функция имеет шесть уровней, вызываемых из семи других мест.
Как мне получить построчное профилирование?
Вместо этого:
function #12, total time: 2.0s
Я хотел бы увидеть что-то вроде этого:
function #12 (called from somefile.py:102) 0.5s
function #12 (called from main.py:12) 1.5s
cProfile показывает, сколько общего времени "переходит" родителю, но опять же это соединение теряется, когда у вас есть несколько слоев и взаимосвязанные вызовы.
В идеале я хотел бы иметь графический интерфейс, который будет анализировать данные, а затем показывать мне мой исходный файл с общим временем, указанным для каждой строки. Что-то вроде этого:
main.py:
a = 1 # 0.0s
result = func(a) # 0.4s
c = 1000 # 0.0s
result = func(c) # 5.0s
Тогда я смогу нажать на второй вызов "func(c)", чтобы увидеть, что занимает время в этом вызове, отдельно от вызова "func(a)".
Имеет ли это смысл? Есть ли библиотека профилирования, которая собирает этот тип информации? Есть ли какой-то потрясающий инструмент, который я пропустил?
3 ответа
Я считаю, что именно для этого предназначен line_profiler Роберта Керна. По ссылке:
File: pystone.py
Function: Proc2 at line 149
Total time: 0.606656 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
149 @profile
150 def Proc2(IntParIO):
151 50000 82003 1.6 13.5 IntLoc = IntParIO + 10
152 50000 63162 1.3 10.4 while 1:
153 50000 69065 1.4 11.4 if Char1Glob == 'A':
154 50000 66354 1.3 10.9 IntLoc = IntLoc - 1
155 50000 67263 1.3 11.1 IntParIO = IntLoc - IntGlob
156 50000 65494 1.3 10.8 EnumLoc = Ident1
157 50000 68001 1.4 11.2 if EnumLoc == Ident1:
158 50000 63739 1.3 10.5 break
159 50000 61575 1.2 10.1 return IntParIO
Надеюсь, это поможет!
Вы также можете использовать pprofile ( pypi). Если вы хотите профилировать все выполнение, это не требует модификации исходного кода. Вы также можете профилировать подмножество более крупной программы двумя способами:
переключать профилирование при достижении определенной точки в коде, например:
import pprofile profiler = pprofile.Profile() with profiler: some_code # Process profile content: generate a cachegrind file and send it to user.
асинхронно переключать профилирование из стека вызовов (требуется способ запуска этого кода в рассматриваемом приложении, например, обработчик сигнала или доступный рабочий поток) с использованием статистического профилирования:
import pprofile profiler = pprofile.StatisticalProfile() statistical_profiler_thread = pprofile.StatisticalThread( profiler=profiler, ) with statistical_profiler_thread: sleep(n) # Likewise, process profile content
Формат вывода аннотации кода очень похож на профилировщик строки:
$ pprofile --threads 0 demo/threads.py
Command line: ['demo/threads.py']
Total duration: 1.00573s
File: demo/threads.py
File duration: 1.00168s (99.60%)
Line #| Hits| Time| Time per hit| %|Source code
------+----------+-------------+-------------+-------+-----------
1| 2| 3.21865e-05| 1.60933e-05| 0.00%|import threading
2| 1| 5.96046e-06| 5.96046e-06| 0.00%|import time
3| 0| 0| 0| 0.00%|
4| 2| 1.5974e-05| 7.98702e-06| 0.00%|def func():
5| 1| 1.00111| 1.00111| 99.54%| time.sleep(1)
6| 0| 0| 0| 0.00%|
7| 2| 2.00272e-05| 1.00136e-05| 0.00%|def func2():
8| 1| 1.69277e-05| 1.69277e-05| 0.00%| pass
9| 0| 0| 0| 0.00%|
10| 1| 1.81198e-05| 1.81198e-05| 0.00%|t1 = threading.Thread(target=func)
(call)| 1| 0.000610828| 0.000610828| 0.06%|# /usr/lib/python2.7/threading.py:436 __init__
11| 1| 1.52588e-05| 1.52588e-05| 0.00%|t2 = threading.Thread(target=func)
(call)| 1| 0.000438929| 0.000438929| 0.04%|# /usr/lib/python2.7/threading.py:436 __init__
12| 1| 4.79221e-05| 4.79221e-05| 0.00%|t1.start()
(call)| 1| 0.000843048| 0.000843048| 0.08%|# /usr/lib/python2.7/threading.py:485 start
13| 1| 6.48499e-05| 6.48499e-05| 0.01%|t2.start()
(call)| 1| 0.00115609| 0.00115609| 0.11%|# /usr/lib/python2.7/threading.py:485 start
14| 1| 0.000205994| 0.000205994| 0.02%|(func(), func2())
(call)| 1| 1.00112| 1.00112| 99.54%|# demo/threads.py:4 func
(call)| 1| 3.09944e-05| 3.09944e-05| 0.00%|# demo/threads.py:7 func2
15| 1| 7.62939e-05| 7.62939e-05| 0.01%|t1.join()
(call)| 1| 0.000423908| 0.000423908| 0.04%|# /usr/lib/python2.7/threading.py:653 join
16| 1| 5.26905e-05| 5.26905e-05| 0.01%|t2.join()
(call)| 1| 0.000320196| 0.000320196| 0.03%|# /usr/lib/python2.7/threading.py:653 join
Обратите внимание, что поскольку pprofile не полагается на модификацию кода, он может профилировать операторы модулей верхнего уровня, позволяя профилировать время запуска программы (сколько времени занимает импорт модулей, инициализация глобальных переменных, ...).
Он может генерировать вывод в формате cachegrind, поэтому вы можете использовать kcachegrind для быстрого просмотра больших результатов.
Раскрытие информации: я являюсь автором профиля.
Просто чтобы улучшить вышеупомянутый ответ @Joe Kington.
Для Python 3.x используйте line_profiler:
Установка:
pip install line_profiler
Применение:
Предположим, у вас есть программа main.py
и в нем функции fun_a()
а также fun_b()
что вы хотите профилировать по времени; вам нужно будет использовать декоратор@profile
непосредственно перед определениями функций. Например,
@profile
def fun_a():
#do something
@profile
def fun_b():
#do something more
if __name__ == '__main__':
fun_a()
fun_b()
Программу можно профилировать, выполнив команду оболочки:
$ kernprof -l -v main.py
Аргументы можно получить, используя $ kernprof -h
Usage: kernprof [-s setupfile] [-o output_file_path] scriptfile [arg] ...
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-l, --line-by-line Use the line-by-line profiler from the line_profiler
module instead of Profile. Implies --builtin.
-b, --builtin Put 'profile' in the builtins. Use 'profile.enable()'
and 'profile.disable()' in your code to turn it on and
off, or '@profile' to decorate a single function, or
'with profile:' to profile a single section of code.
-o OUTFILE, --outfile=OUTFILE
Save stats to <outfile>
-s SETUP, --setup=SETUP
Code to execute before the code to profile
-v, --view View the results of the profile in addition to saving
it.
Результаты будут напечатаны на консоли как:
Total time: 17.6699 s
File: main.py
Function: fun_a at line 5
Line # Hits Time Per Hit % Time Line Contents
==============================================================
5 @profile
6 def fun_a():
...
EDIT: результаты профилировщиков можно проанализировать с помощью пакета TAMPPA. Используя его, мы можем построчно получать желаемые графики как
Вы можете воспользоваться помощью пакета line_profiler для этого
1. Сначала установите пакет:
pip install line_profiler
2. Используйте волшебную команду для загрузки пакета в вашу среду Python/ Notebook.
%load_ext line_profiler
3. Если вы хотите профилировать коды для функции, то
сделать следующее: % lprun -f имя_функции имя_функции
%lprun -f function_defined_by_you function_defined_by_you(arg1, arg2)
ВЫ ПОЛУЧИТЕ ОТЛИЧНЫЙ ФОРМАТИРОВАННЫЙ ВЫХОД СО ВСЕМИ ДЕТАЛЯМИ, ЕСЛИ ВЫ СЛЕДУЕТЕ ВЫШЕПРИ ВЫШЕ ШАГОВ
PyVmMonitor имеет просмотр в реальном времени, который может вам помочь (вы можете подключиться к работающей программе и получить статистику по ней).
Смотрите: http://www.pyvmmonitor.com/