Dans Python 2.6.6, nous avons comparé les temps d'exécution des méthodes 1,2,3 suivantes pour ajouter 10 000 000 d'entiers. En conclusion, la notation d'inclusion de liste de 3 était aussi rapide que prévu. Comme je l'ai écrit dans le post-scriptum, le contenu décrit ici peut également être appliqué aux séries Python 2.7 et 3.3.
# 1. testfunc1:Préparez une liste vide et ajoutez
def testfunc1(rangelist):
templist = []
for temp in rangelist:
templist.append(temp)
# 2. testfunc2: 1+Objectify append
def testfunc2(rangelist):
templist = []
append = templist.append
for temp in rangelist:
append(temp)
# 3. testfunc3:Notation d'inclusion de liste
def testfunc3(rangelist):
templist = [temp for temp in rangelist]
Utilisez la commande% timeit d'IPython pour exécuter 10 fois "Extraire le plus rapide des 3 exécutions" pour trouver le temps d'exécution moyen.
# 10,000,Créer une liste de 000 entiers
>>> rangelist = range(1,10000000)
>>> %timeit -n 10 -r 3 testfunc1(rangelist)
10 loops, best of 3: 1.73 s per loop
>>> %timeit -n 10 -r 3 testfunc2(rangelist)
10 loops, best of 3: 1.08 s per loop
>>> %timeit -n 10 -r 3 testfunc3(rangelist)
10 loops, best of 3: 697 ms per loop
** Dans une boucle où le temps d'exécution est un problème, il est préférable d'utiliser une notation d'inclusion de liste telle que 3. ** ** ** Même si vous n'utilisez pas la notation d'inclusion de liste, il est préférable de mettre la référence de la méthode append dans la variable à l'avance comme dans 2. ** **
Notez que la deuxième méthode consistant à placer la référence de méthode dans une variable à l'avance peut également être utilisée pour raccourcir le temps d'exécution de la boucle autre que la création de liste.
Examinons pourquoi ce résultat est obtenu en désassemblant chaque fonction à l'aide du module dis de Python.
>>> import dis
>>> dis.dis(testfunc1)
3 0 BUILD_LIST 0
3 STORE_FAST 1 (templist)
4 6 SETUP_LOOP 27 (to 36)
9 LOAD_FAST 0 (rangelist)
12 GET_ITER
>> 13 FOR_ITER 19 (to 35)
16 STORE_FAST 2 (temp)
5 19 LOAD_FAST 1 (templist)
22 LOAD_ATTR 0 (append)
25 LOAD_FAST 2 (temp)
28 CALL_FUNCTION 1
31 POP_TOP
32 JUMP_ABSOLUTE 13
>> 35 POP_BLOCK
>> 36 LOAD_CONST 0 (None)
39 RETURN_VALUE
>>> dis.dis(testfunc2)
9 0 BUILD_LIST 0
3 STORE_FAST 1 (templist)
10 6 LOAD_FAST 1 (templist)
9 LOAD_ATTR 0 (append)
12 STORE_FAST 2 (append)
11 15 SETUP_LOOP 24 (to 42)
18 LOAD_FAST 0 (rangelist)
21 GET_ITER
>> 22 FOR_ITER 16 (to 41)
25 STORE_FAST 3 (temp)
12 28 LOAD_FAST 2 (append)
31 LOAD_FAST 3 (temp)
34 CALL_FUNCTION 1
37 POP_TOP
38 JUMP_ABSOLUTE 22
>> 41 POP_BLOCK
>> 42 LOAD_CONST 0 (None)
45 RETURN_VALUE
>>> dis.dis(testfunc3)
16 0 BUILD_LIST 0
3 DUP_TOP
4 STORE_FAST 1 (_[1])
7 LOAD_FAST 0 (rangelist)
10 GET_ITER
>> 11 FOR_ITER 13 (to 27)
14 STORE_FAST 2 (temp)
17 LOAD_FAST 1 (_[1])
20 LOAD_FAST 2 (temp)
23 LIST_APPEND
24 JUMP_ABSOLUTE 11
>> 27 DELETE_FAST 1 (_[1])
30 STORE_FAST 3 (templist)
33 LOAD_CONST 0 (None)
36 RETURN_VALUE
De la sortie ci-dessus, FOR_ITER à JUMP_ABSOLUTE est une boucle appelée 10 000 000 fois.
Dans testfunc1, vous pouvez voir que 22 LOAD_ATTR appelle la méthode append de l'objet de liste, puis 28 CALL_FUNCTION ajoute réellement.
testfunc1 pour la boucle
>> 13 FOR_ITER 19 (to 35)
16 STORE_FAST 2 (temp)
5 19 LOAD_FAST 1 (templist)
22 LOAD_ATTR 0 (append)
25 LOAD_FAST 2 (temp)
28 CALL_FUNCTION 1
31 POP_TOP
32 JUMP_ABSOLUTE 13
D'autre part, dans testfunc2, LOAD_ATTR est hors de la boucle, et à l'intérieur de la boucle, 28 LOAD_FAST appelle la méthode append, puis 34 CALL_FUNCTION exécute append.
testfunc2 pour la boucle
>> 22 FOR_ITER 16 (to 41)
25 STORE_FAST 3 (temp)
12 28 LOAD_FAST 2 (append)
31 LOAD_FAST 3 (temp)
34 CALL_FUNCTION 1
37 POP_TOP
38 JUMP_ABSOLUTE 22
LOAD_ATTR et LOAD_FAST prennent plus de temps que LOAD_ATTR. Par conséquent, testfunc2 avec LOAD_ATTR en dehors de la boucle a un temps d'exécution plus court que testfunc1 avec LOAD_ATTR à l'intérieur de la boucle.
Dans testfunc2, j'ai chargé la méthode append avec 28 LOAD_FAST, puis je l'ai appelée avec 34 CALL_FUNCTION. D'autre part, testfunc3 appelle 23 LIST_APPEND, un processus de liste uniquement, et il n'y a pas de CALL_FUNCTION.
testfunc3 pour la boucle
>> 11 FOR_ITER 13 (to 27)
14 STORE_FAST 2 (temp)
17 LOAD_FAST 1 (_[1])
20 LOAD_FAST 2 (temp)
23 LIST_APPEND
24 JUMP_ABSOLUTE 11
Pour CALL_FUNCTION et LIST_APPEND, l'appel de LIST_APPEND, qui est un processus de liste uniquement, est plus rapide (CALL_FUNCTION a différents processus car il s'agit d'un appel de fonction général). Par conséquent, testfunc3 avec CALL_FUNCTION remplacé par LIST_APPEND a un temps d'exécution plus court que testfunc2 avec CALL_FUNCTION dans la boucle.
Cet article a été rédigé en référence aux articles suivants.
J'ai également comparé Python 2.7 et 3.3 pour m'assurer que ce qui précède ne concerne pas uniquement Python 2.6. Depuis que j'ai changé la machine, il ne sert à rien de la comparer avec le temps d'exécution ci-dessus.
Python 2.7.4
>>> %timeit -n 5 testfunc1()
5 loops, best of 3: 974 ms per loop
>>> %timeit -n 5 testfunc2()
5 loops, best of 3: 737 ms per loop
>>> %timeit -n 5 testfunc3()
5 loops, best of 3: 430 ms per loop
Python 3.3.3
>>> %timeit -n 5 testfunc1()
5 loops, best of 3: 1.21 s per loop
>>> %timeit -n 5 testfunc2()
5 loops, best of 3: 815 ms per loop
>>> %timeit -n 5 testfunc3()
5 loops, best of 3: 639 ms per loop
Eh bien, Python3 est plus lent ... Au fait, dans Python3, il semble que la fonction range renvoie un itérateur au lieu d'une liste, donc créons et exécutons une liste sans utiliser la fonction range.
>>> %timeit -n 5 testfunc1()
5 loops, best of 3: 1.19 s per loop
>>> %timeit -n 5 testfunc2()
5 loops, best of 3: 611 ms per loop
>>> %timeit -n 5 testfunc3()
5 loops, best of 3: 429 ms per loop
Je me méfie toujours de testfunc1, mais maintenant je peux exécuter testfunc2 et testfunc3 dans à peu près le même laps de temps que Python 2.7.4.
La même chose peut être dite pour Python 2.7 et Python 3.3 pour 1, 2 et 3 comme pour Python 2.6.
Recommended Posts