Как я могу профилировать код Python построчно?


116

Я использую 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)».

Имеет ли это смысл? Есть ли какая-либо библиотека профилирования, которая собирает такую ​​информацию? Есть какой-нибудь замечательный инструмент, который я пропустил?


2
Думаю, вам это будет интересно pstats.print_callers. Пример здесь .
Мухаммад Алькарури,

Мухаммад, это определенно полезно! По крайней мере, это решает одну проблему: разделение вызовов функций в зависимости от происхождения. Я думаю, что ответ Джо Кингтона ближе к моей цели, но print_callers () определенно помогает мне на полпути. Спасибо!
rocketmonkeys

Ответы:


120

Я считаю, что это то, для чего предназначен 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

Надеюсь, это поможет!


10
Line_profiler работает с Python 3? Я не мог получить по этому поводу никакой информации.
user1251007

3
line_profiler не показывает для меня совпадения и время. Кто-нибудь может сказать мне, почему? А как решить?
I159 06

6
Вот декоратор, который я написал: gist.github.com/kylegibson/6583590 . Если вы запускаете тесты на нос, обязательно используйте параметр -s, чтобы сразу распечатать stdout.
Кайл Гибсон

5
как выглядит скрипт python, который производит этот вывод? import line_profiler;а потом ?
Жубарб

10
кто-нибудь может показать, как на самом деле использовать эту библиотеку? Readme учит, как установить, и отвечает на различные часто задаваемые вопросы, но не упоминает, как использовать его после установки pip ..
cryanbhu

47

Вы также можете использовать pprofile ( pypi ). Если вы хотите профилировать все выполнение, это не требует модификации исходного кода. Вы также можете профилировать подмножество более крупной программы двумя способами:

  • переключать профилирование при достижении определенной точки в коде, например:

    import pprofile
    profiler = pprofile.Profile()
    with profiler:
        some_code
    # Process profile content: generate a cachegrind file and send it to user.
    
    # You can also write the result to the console:
    profiler.print_stats()
    
    # Or to a file:
    profiler.dump_stats("/tmp/profiler_stats.txt")
  • асинхронно переключить профилирование из стека вызовов (требуется способ запуска этого кода в рассматриваемом приложении, например обработчик сигнала или доступный рабочий поток) с помощью статистического профилирования:

    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 для просмотра больших результатов.

Раскрытие информации: я автор профиля.


1
+1 Спасибо за ваш вклад. Выглядит хорошо сделанным. У меня немного другая точка зрения: одна цель - измерение времени, затрачиваемого на утверждения и функции. Другая цель - выяснить, что можно сделать для ускорения кода. Разница становится до боли очевидной по мере того, как код становится большим - например, 10 ^ 6 строк кода. Код может терять много времени. Я нахожу это путем взятия небольшого количества очень подробных образцов и изучения их человеческим глазом, а не обобщения. Проблема проявляется в той доле времени, которую она тратит впустую.
Майк Данлэви

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

3
Это именно то, что я искал: ненавязчивый и обширный.
egpbos 05

1
Хороший инструмент, но он работает в несколько раз медленнее исходного кода.
rominf

4

Для этого вы можете воспользоваться помощью пакета line_profiler

1. Сначала установите пакет:

    pip install line_profiler

2. Используйте волшебную команду, чтобы загрузить пакет в среду Python / Notebook.

    %load_ext line_profiler

3. Если вы хотите профилировать коды для функции,
сделайте следующее:

    %lprun -f demo_func demo_func(arg1, arg2)

вы получите красивый форматированный вывод со всеми подробностями, если выполните следующие действия :)

Line #      Hits      Time    Per Hit   % Time  Line Contents
 1                                           def demo_func(a,b):
 2         1        248.0    248.0     64.8      print(a+b)
 3         1         40.0     40.0     10.4      print(a)
 4         1         94.0     94.0     24.5      print(a*b)
 5         1          1.0      1.0      0.3      return a/b

4

Просто чтобы улучшить вышеупомянутый ответ @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():
...

РЕДАКТИРОВАТЬ: результаты профилировщиков можно проанализировать с помощью пакета TAMPPA . Используя его, мы можем построчно получать желаемые графики как участок


Инструкции точны, но график вводит в заблуждение, потому line_profilerчто не профилирует использование памяти ( memory_profilerделает, но часто дает сбой). Я бы рекомендовал использовать (мой) ЛЕСТНИЧНОЙ профайлер вместо этого, если вы на Mac OS X или Linux: pip install -U scalene, github.com/emeryberger/scalene - (! И больше) он одновременно делает линейный уровень профилирование времени процессора и памяти .
ЭмериБергер,

Привет @emeryberger, показанный график создан новым пакетом: TAMPPA. хотя это предмет вопросов. Я знаю, что есть много способов. Спасибо, что поделились одним. Я бы порекомендовал отправить здесь подробный ответ :) Вы отправляли проблему для memory_profiler?
Пе Дро

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.