Как узнать, какие части моего кода неэффективны в Python

на предыдущем вопросе я спрашивал о многопроцессорной обработке, используя несколько ядер, чтобы программа работала быстрее, и кто-то сказал мне это:

чаще всего вы можете получить оптимизацию 100x+ с лучшим кодом по сравнению с улучшением 4x и дополнительными сложностями с многопроцессорной

затем они рекомендовали мне:

использовать профайлер, чтобы понять, что медленно, а затем сосредоточиться на оптимизации что.

поэтому я пошел на этот вопрос: как вы можете профилировать сценарий?

здесь я нашел cProfile и реализовал его в некоторый тестовый код, чтобы посмотреть, как он работает.

это мой код:

import cProfile

def foo():
    for i in range(10000):
        a = i**i
        if i % 1000 == 0:
            print(i)

cProfile.run('foo()')

однако, после запуска, это было то, что я получил:

0
1000
2000
3000
4000
5000
6000
7000
8000
9000
         1018 function calls in 20.773 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   20.773   20.773 <string>:1(<module>)
      147    0.000    0.000    0.000    0.000 rpc.py:150(debug)
       21    0.000    0.000    0.050    0.002 rpc.py:213(remotecall)
       21    0.000    0.000    0.002    0.000 rpc.py:223(asynccall)
       21    0.000    0.000    0.048    0.002 rpc.py:243(asyncreturn)
       21    0.000    0.000    0.000    0.000 rpc.py:249(decoderesponse)
       21    0.000    0.000    0.048    0.002 rpc.py:287(getresponse)
       21    0.000    0.000    0.000    0.000 rpc.py:295(_proxify)
       21    0.001    0.000    0.048    0.002 rpc.py:303(_getresponse)
       21    0.000    0.000    0.000    0.000 rpc.py:325(newseq)
       21    0.000    0.000    0.002    0.000 rpc.py:329(putmessage)
       21    0.000    0.000    0.000    0.000 rpc.py:55(dumps)
       20    0.000    0.000    0.001    0.000 rpc.py:556(__getattr__)
        1    0.000    0.000    0.001    0.001 rpc.py:574(__getmethods)
       20    0.000    0.000    0.000    0.000 rpc.py:598(__init__)
       20    0.000    0.000    0.050    0.002 rpc.py:603(__call__)
       20    0.000    0.000    0.051    0.003 run.py:340(write)
        1   20.722   20.722   20.773   20.773 test.py:3(foo)
       42    0.000    0.000    0.000    0.000 threading.py:1226(current_thread)
       21    0.000    0.000    0.000    0.000 threading.py:215(__init__)
       21    0.000    0.000    0.047    0.002 threading.py:263(wait)
       21    0.000    0.000    0.000    0.000 threading.py:74(RLock)
       21    0.000    0.000    0.000    0.000 {built-in method _struct.pack}
       21    0.000    0.000    0.000    0.000 {built-in method _thread.allocate_lock}
       42    0.000    0.000    0.000    0.000 {built-in method _thread.get_ident}
        1    0.000    0.000   20.773   20.773 {built-in method builtins.exec}
       42    0.000    0.000    0.000    0.000 {built-in method builtins.isinstance}
       63    0.000    0.000    0.000    0.000 {built-in method builtins.len}
       10    0.000    0.000    0.051    0.005 {built-in method builtins.print}
       21    0.000    0.000    0.000    0.000 {built-in method select.select}
       21    0.000    0.000    0.000    0.000 {method '_acquire_restore' of '_thread.RLock' objects}
       21    0.000    0.000    0.000    0.000 {method '_is_owned' of '_thread.RLock' objects}
       21    0.000    0.000    0.000    0.000 {method '_release_save' of '_thread.RLock' objects}
       21    0.000    0.000    0.000    0.000 {method 'acquire' of '_thread.RLock' objects}
       42    0.047    0.001    0.047    0.001 {method 'acquire' of '_thread.lock' objects}
       21    0.000    0.000    0.000    0.000 {method 'append' of 'collections.deque' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
       21    0.000    0.000    0.000    0.000 {method 'dump' of '_pickle.Pickler' objects}
       20    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
       21    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.BytesIO' objects}
       21    0.000    0.000    0.000    0.000 {method 'release' of '_thread.RLock' objects}
       21    0.001    0.000    0.001    0.000 {method 'send' of '_socket.socket' objects}

Я ожидал, что он покажет мне, какие части моего кода занимают больше всего времени, например, чтобы показать, что a = i**i был дольше вычислить, однако все, что я могу собрать из того, что он сказал мне, что foo() функция заняла самое длинное, однако то, что заняло самое длинное внутри этой функции, я понятия не имею из данных.

кроме того, когда я реализую это в мой фактический код, он делает то же самое. Все в функциях, и это только говорит мне, какие функции занимают больше времени, а не то, что в функции занимает так много времени.

Итак, вот мои основные вопросы:

  1. как я вижу, что внутри функции делает код так долго (должен ли я даже использовать cProfile?)

  2. каков наилучший способ оптимизации моего кода, как только я узнаю, что использует самый CPU

Примечание: мой RAM и диск и т. д. абсолютно прекрасны, это просто процессор, который максимизирован (12% CPU, потому что он работает только на одном ядре)

2 ответов


как я вижу, что внутри функции делает код так долго (должен ли я даже использовать cProfile?)

Да, вы можете использовать cProfile но то, как вы задали вопрос, заставляет меня задуматься, если line_profiler (сторонний модуль, вам нужно установить его) не был бы лучшим инструментом.

я использую привязки IPython/Jupyter этого пакета, когда я хочу профилировать функцию:

%load_ext line_profiler

фактически профиль a функция:

%lprun -f foo foo()
#             ^^^^^---- this call will be profiled
#         ^^^-----------function to profile

который производит этот выход:

Timer unit: 5.58547e-07 s

Total time: 17.1189 s
File: <ipython-input-1-21b5a5f52f66>
Function: foo at line 1

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def foo():
     2     10001        31906      3.2      0.1      for i in range(10000):
     3     10000     30534065   3053.4     99.6          a = i**i
     4     10000        75998      7.6      0.2          if i % 1000 == 0:
     5        10         6953    695.3      0.0              print(i)

это включает в себя несколько вещей, которые могут быть интересны. Например 99.6% время, проведенное в i**i линии.

  1. каков наилучший способ оптимизации моего кода, как только я узнаю, что использует самый CPU

что зависит. Иногда вам нужно использовать разные функции / структуры данных / алгоритмы-иногда вы не можете этого сделать что угодно. Но, по крайней мере, вы знаете, где находится ваше узкое место, и можете оценить, насколько сильно повлияет изменение в узком месте или где-то еще.


Как вы заметили в журнале профилирования,cProfile максимальное разрешение составляет функции.

Так:

  • если ваша функция мала, вы можете понять, какая часть занимает много времени (хотя иногда это сложно со встроенными вызовами, такими как in)
  • если ваша функция большая, возможно, пришло время сократить ее до меньших функций, которые становятся "профилируемыми", однако накладные расходы на вызов функции могут замедлить работу вниз!--10-->