Voyons comment utiliser un profileur en Python pour chronométrer et accélérer un programme.
Qu'est-ce que l'optimisation de programme?
Cette fois, nous nous concentrerons sur la réduction du temps de traitement
Mesurons le temps de traitement du programme.
En supposant que vous ayez deux programmes qui prennent 5 secondes, essayez de mesurer avec la commande de temps.
python
% time ./wait.py
./wait.py 0.02s user 0.02s system 0% cpu 5.057 total
% time ./busy.py
./busy.py 5.01s user 0.02s system 99% cpu 5.038 total
Dans les deux cas, il faut un total de 5 secondes du début à la fin du programme, mais la situation est légèrement différente.
Jetons un coup d'œil à la source réellement utilisée:
wait.py
#!/usr/bin/env python
import time
def main():
time.sleep(5)
if __name__ == '__main__':
main()
busy.py
#!/usr/bin/env python
import time
def main():
start = time.time()
while time.time() - start < 5:
pass
if __name__ == '__main__':
main()
Comment utiliser le temps est différent
En général, les programmes passent leur temps à faire des choses comme:
Améliorez la façon dont vous passez votre temps
Où pourriez-vous vous améliorer dans le programme?
Comment trouver
Sortie journal du décalage horaire avant et après traitement
python
start = time.time()
some_func()
print "%f sec" % (time.time() - start)
python
start = time.clock()
some_func()
print "%f sec" % (time.clock() - start)
cProfile
Un des outils appelé "Profiler" fourni avec Python.
Exécuter avec un programme à exécuter comme la commande time en argument
python
% python -m cProfile wait.py
4 function calls in 5.002 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 5.002 5.002 wait.py:3(<module>)
1 0.000 0.000 5.001 5.001 wait.py:5(main)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 5.001 5.001 5.001 5.001 {time.sleep}
article | Signification de la valeur |
---|---|
ncalls | Nombre d'appels |
tottime | Temps passé(N'inclut pas ce qui a été appelé) |
percall | Temps passé par appel(tottime/ncalls) |
cumtime | Temps passé(Y compris ce que vous avez appelé) |
percall | Temps passé par appel(cumtime/ncalls) |
Lorsque vous exécutez cProfile.py à partir de la ligne de commande
wait_profile.py
#!/usr/bin/env python
import cProfile
import time
def main():
time.sleep(5)
if __name__ == '__main__':
cProfile.run("main()", "wait.prof")
% python -c "import pstats; pstats.Stats('wait.prof').strip_dirs().sort_stats(-1).print_stats()"
Fri Jun 17 00:25:58 2016 wait.prof
4 function calls in 5.005 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 5.005 5.005 <string>:1(<module>)
1 0.000 0.000 5.005 5.005 wait_profile.py:6(main)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 5.005 5.005 5.005 5.005 {time.sleep}
Améliorer les exigences non fonctionnelles tout en maintenant les exigences fonctionnelles
Comment recommander
Séquence de Fibonacci
fib.py
#!/usr/bin/env python
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-2) + fib(n-1)
if __name__ == '__main__':
assert fib(30) == 832040
python
% time ./fib.py
python fib.py 0.52s user 0.01s system 98% cpu 0.540 total
% python -m cProfile fib.py
2692539 function calls (3 primitive calls) in 1.084 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 1.084 1.084 fib.py:3(<module>)
2692537/1 1.084 0.000 1.084 1.084 fib.py:3(fib)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Dans cet exemple, fib est appelé trop souvent. (M / N de tottime signifie M appels récursifs et N appels non récursifs)
Notez également que ce dernier prend plus de temps lorsque l'on compare le temps d'exécution sans profil (0,540s) mesuré avec la commande de temps et le temps d'exécution avec profil (1,084s).
Améliorez les performances tout en maintenant la fonctionnalité
Essayez en fait
fib_optimized.py
#!/usr/bin/env python
cache = {}
def fib(n):
if n in cache:
return cache[n]
if n == 0:
cache[n] = 0
elif n == 1:
cache[n] = 1
else:
cache[n] = fib(n-2) + fib(n-1)
return cache[n]
if __name__ == '__main__':
assert fib(30) == 832040
python
% python -m cProfile fib_optimized.py
61 function calls (3 primitive calls) in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 fib_optimized.py:3(<module>)
59/1 0.000 0.000 0.000 0.000 fib_optimized.py:5(fib)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Le nombre d'appels a été réduit et le temps global a été raccourci!
Le deuxième exemple. Une fonction qui prend la somme du début à la fin est implémentée à l'aide de la fonction standard sum.
takesum.py
#!/usr/bin/env python
def takesum(beg, end):
"take sum of beg, beg+1, ..., end"
assert beg <= end
i = beg
xs = []
while i <= end:
xs.append(i)
i += 1
return sum(xs)
if __name__ == '__main__':
assert takesum(0, 10000000) == 50000005000000
python
% python -m cProfile takesum.py
10000005 function calls in 3.482 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.076 0.076 3.482 3.482 takesum.py:3(<module>)
1 2.418 2.418 3.405 3.405 takesum.py:3(takesum)
10000001 0.878 0.000 0.878 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.109 0.109 0.109 0.109 {sum}
Réfléchissons à la façon dont nous pouvons réduire le temps de traitement.
Recommended Posts