Как узнать, какие части моего кода неэффективны в 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()
функция заняла самое длинное, однако то, что заняло самое длинное внутри этой функции, я понятия не имею из данных.
кроме того, когда я реализую это в мой фактический код, он делает то же самое. Все в функциях, и это только говорит мне, какие функции занимают больше времени, а не то, что в функции занимает так много времени.
Итак, вот мои основные вопросы:
как я вижу, что внутри функции делает код так долго (должен ли я даже использовать
cProfile
?)каков наилучший способ оптимизации моего кода, как только я узнаю, что использует самый 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
линии.
- каков наилучший способ оптимизации моего кода, как только я узнаю, что использует самый CPU
что зависит. Иногда вам нужно использовать разные функции / структуры данных / алгоритмы-иногда вы не можете этого сделать что угодно. Но, по крайней мере, вы знаете, где находится ваше узкое место, и можете оценить, насколько сильно повлияет изменение в узком месте или где-то еще.
Как вы заметили в журнале профилирования,cProfile
максимальное разрешение составляет функции.
Так:
- если ваша функция мала, вы можете понять, какая часть занимает много времени (хотя иногда это сложно со встроенными вызовами, такими как
in
) - если ваша функция большая, возможно, пришло время сократить ее до меньших функций, которые становятся "профилируемыми", однако накладные расходы на вызов функции могут замедлить работу вниз!--10-->