Sur une question précédente, je posais des questions sur le multitraitement, l'utilisation de plusieurs cœurs pour accélérer l'exécution d'un programme, et quelqu'un m'a dit ceci:
Plus souvent qu'autrement, vous pouvez obtenir une optimisation 100x + avec un meilleur code par rapport à une amélioration 4x et des complexités supplémentaires avec le multitraitement
Ils m'ont ensuite recommandé:
Utilisez un profileur pour comprendre ce qui est lent, puis concentrez-vous sur l'optimisation de cela.
Je suis donc allé à cette question: Comment pouvez-vous profiler un script?
Ici, j'ai trouvé cProfile
et l'ai implémenté dans du code de test pour voir comment cela fonctionne.
Voici mon code:
import cProfile
def foo():
for i in range(10000):
a = i**i
if i % 1000 == 0:
print(i)
cProfile.run('foo()')
Cependant, après l'avoir exécuté, c'est ce que j'ai obtenu:
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}
Je m'attendais à ce qu'il me montre quelles parties de mon code prenaient le plus de temps, par exemple pour montrer que a = i**i
Prenait le plus de temps à calculer, mais tout ce que je peux tirer de ce qu'il m'a dit, c'est que la fonction foo()
a pris le plus de temps, mais ce qui a pris le plus de temps à l'intérieur de cette fonction, je n'ai aucune idée des données.
De plus, lorsque j'implémente cela dans mon code réel, cela fait la même chose. Tout est dans les fonctions, et cela me dit seulement quelles fonctions prennent le plus de temps plutôt que ce qui prend si longtemps dans la fonction.
Voici donc mes principales questions:
Comment puis-je voir ce qui, à l'intérieur de la fonction, rend le code si long (devrais-je même utiliser cProfile
?)
Quelle est la meilleure façon d'optimiser mon code une fois que je sais ce qui utilise le plus de CPU
Remarque: Mon RAM et le disque etc. sont tout à fait corrects, c'est juste le CPU qui est au maximum (12% CPU car il ne fonctionne que sur un seul cœur)
Comment puis-je voir ce qui, à l'intérieur de la fonction, rend le code si long (devrais-je même utiliser cProfile?)
Oui, vous pouvez utiliser cProfile
mais la façon dont vous avez posé la question me fait me demander si line_profiler
(module tiers, vous devez l'installer) ne serait pas un meilleur outil.
J'utilise les liaisons IPython/Jupyter de ce package lorsque je veux profiler une fonction:
%load_ext line_profiler
Pour réellement profiler une fonction:
%lprun -f foo foo()
# ^^^^^---- this call will be profiled
# ^^^-----------function to profile
Ce qui produit cette sortie:
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)
Cela comprend plusieurs choses qui pourraient être intéressantes. Par exemple 99.6%
du temps est passé dans le i**i
ligne.
- Quelle est la meilleure façon d'optimiser mon code une fois que je sais ce qui utilise le plus de CPU
Ça dépend. Parfois, vous devez utiliser différentes fonctions/infrastructures de données/algorithmes - parfois, vous ne pouvez rien faire. Mais au moins, vous savez où se trouve votre goulot d'étranglement et vous pouvez estimer l'impact d'un changement au goulot d'étranglement ou ailleurs.
Comme vous l'avez remarqué dans le journal de profilage, la résolution maximale cProfile
est la fonction.
Alors:
in
)