Як я можу профайлювати код 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? Я не міг отримати про це ніякої інформації.
користувач1251007

3
line_profiler не показує хіти і час для мене. Хтось може мені сказати, чому? І як вирішити?
I159

6
Ось декоратор, про який я написав: gist.github.com/kylegibson/6583590 . Якщо ви проводите нос-тести, не забудьте скористатися опцією -s, щоб stdout надрукувався негайно.
Кайл Гібсон

5
як виглядає сценарій python, який виробляє цей вихід? import line_profiler;і потім ?
Жубарб

10
хтось може показати, як насправді використовувати цю бібліотеку? Програма readme вчить як встановити та відповідати на різні поширені запитання, але не згадує, як її використовувати після встановлення
файлу

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 для легкого перегляду великих результатів.

Розкриття: Я автор pprofile.


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

1
Ви маєте рацію, я не згадував про використання pprofile, коли хочеться профілювати менший набір. Я редагував свою публікацію, щоб додати приклади цього.
vpelletier

3
Це саме те, що я шукав: ненав'язливий та обширний.
egpbos

1
Хороший інструмент, але він працює в кілька разів повільніше, ніж оригінальний код.
rominf

4

Для цього ви можете скористатися пакетом line_profiler

1. Перший встановіть пакет:

    pip install line_profiler

2. Використовуйте магічну команду для завантаження пакета у ваше середовище пітон / ноутбук

    %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 . Використовуючи його, ми можемо отримати рядкові бажані сюжети як сюжет


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.