2011-03-25 4 views
5

Je voulais voir à quel point la réduction était plus rapide que d'utiliser une boucle for pour des opérations numériques simples. Voici ce que je trouve (en utilisant la bibliothèque standard timeit):Sommer avec une boucle for plus vite qu'avec réduire?

In [54]: print(setup) 
from operator import add, iadd 
r = range(100) 

In [55]: print(stmt1)  
c = 0 
for i in r: 
    c+=i   

In [56]: timeit(stmt1, setup) 
Out[56]: 8.948904991149902 
In [58]: print(stmt3)  
reduce(add, r)  

In [59]: timeit(stmt3, setup) 
Out[59]: 13.316915035247803 

Vous cherchez un peu plus:

In [68]: timeit("1+2", setup) 
Out[68]: 0.04145693778991699 

In [69]: timeit("add(1,2)", setup) 
Out[69]: 0.22807812690734863 

Qu'est-ce qui se passe ici? Évidemment, réduire boucle plus vite que pour, mais l'appel de fonction semble dominer. La version réduite ne devrait-elle pas fonctionner presque entièrement en C? L'utilisation de iadd (c, i) dans la version de la boucle for le fait fonctionner en ~ 24 secondes. Pourquoi utiliser operator.add serait tellement plus lent que +? J'étais sous l'impression + et operator.add exécutait le même code C (j'ai vérifié pour m'assurer que operator.add n'appelait pas seulement + dans python ou quoi que ce soit).

BTW, juste en utilisant la somme s'exécute en ~ 2,3 secondes.

In [70]: print(sys.version) 
2.7.1 (r271:86882M, Nov 30 2010, 09:39:13) 
[GCC 4.0.1 (Apple Inc. build 5494)] 
+0

Le fait que l'utilisation 'sum' fait le travail 4 fois plus rapide est à peu près un indicateur que « il devrait y avoir une façon évidente de le faire ». – jsbueno

+0

@jsbbueno: C'est vrai, mais je faisais ça pour trouver le moyen le plus rapide de faire des calculs numériques généraux sur des séquences. Dans le monde réel, j'utiliserais certainement la somme pour additionner: D N'a pas essayé mul, mais je suis sûr que les résultats seraient similaires. –

Répondre

5

Le reduce(add, r) doit appeler la fonction add() 100 fois , de sorte que les frais généraux des appels de fonction ajoute - réduire les utilisations PyEval_CallObject d'invoquer add à chaque itération:

for (;;) { 
    ... 
    if (result == NULL) 
     result = op2; 
    else { 
     # here it is creating a tuple to pass the previous result and the next 
     # value from range(100) into func add(): 
     PyTuple_SetItem(args, 0, result); 
     PyTuple_SetItem(args, 1, op2); 
     if ((result = PyEval_CallObject(func, args)) == NULL) 
      goto Fail; 
    } 

Mise à jour: Réponse faire la queue stion dans les commentaires.

Lorsque vous tapez 1 + 2 dans le code source Python, le compilateur bytecode effectue l'addition en place et remplace cette expression avec 3:

f1 = lambda: 1 + 2 
c1 = byteplay.Code.from_code(f1.func_code) 
print c1.code 

1   1 LOAD_CONST   3 
      2 RETURN_VALUE   

Si vous ajoutez deux variables a + b le compilateur génère bytecode qui charge les deux variables et effectue une BINARY_ADD, ce qui est beaucoup plus rapide que d'appeler une fonction pour effectuer l'addition:

f2 = lambda a, b: a + b 
c2 = byteplay.Code.from_code(f2.func_code) 
print c2.code 

1   1 LOAD_FAST   a 
      2 LOAD_FAST   b 
      3 BINARY_ADD   
      4 RETURN_VALUE   
+0

Merci d'avoir signalé cela! Cependant, cela n'explique pas pourquoi le '1 + 2' brut est 5 fois plus rapide que 'ajouter (1,2)'. En effet, la réduction est beaucoup plus rapide que lors de l'utilisation de iadd dans le. –

+0

J'ai mis à jour ma réponse avec plus de détails. – samplebias

+1

Pourquoi vos exemples utilisent-ils un module tiers au lieu du module 'dis' intégré? –

0

Il pourrait être les frais généraux de la copie args et renvoyer des valeurs (par exemple « ajouter (1, 2) »), opposé à la simple opération sur les littéraux numériques

0

Si réduire est utilisé pour l'addition NumPy ar rayons par index, il peut être plus rapide qu'une boucle for.

from functools import reduce 
from numpy import array, arange 
from time import time 

def add(x, y): 
    return x + y 

def sum_columns(x): 
    if x.any(): 
     width = len(x[0]) 
     total = array([0] * width) 
    for row in x: 
     total += array(row) 
    return total 

l = arange(3000000) 
l = array([l, l, l]) 

start = time() 
print(reduce(add, l)) 
print('Reduce took {}'.format(time() - start)) 

start = time() 
print(sum_columns(l)) 
print('For loop took took {}'.format(time() - start)) 

Avec le résultat de

[  0  3  6 ..., 8999991 8999994 8999997] 
Reduce took 0.024930953979492188 
[  0  3  6 ..., 8999991 8999994 8999997] 
For loop took took 0.3731539249420166