2011-03-25 10 views
5

Quería ver cuánto más rápido se reduce que usar un bucle for para operaciones numéricas simples. Esto es lo que he encontrado (usando la biblioteca timeit estándar):¿Sumar con un bucle for más rápido que con reducir?

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 

Mirando un poco más:

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

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

¿Qué está pasando aquí? Obviamente, reducir does loop más rápido que para, pero la llamada a la función parece dominar. ¿No debería la versión reducida funcionar casi por completo en C? Usar iadd (c, i) en la versión for loop lo hace funcionar en ~ 24 segundos. ¿Por qué usar operator.add sería mucho más lento que +? Tenía la impresión de que + operator.add ejecutaba el mismo código C (lo comprobé para asegurarme de que operator.add no estaba simplemente llamando a + en python ni nada).

Por cierto, solo con la suma de carreras en ~ 2.3 segundos.

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

El hecho de que el uso de 'sum' hace el trabajo 4 veces más rápido es más o menos un indicador de que "debe haber una manera obvia de hacerlo". – jsbueno

+0

@jsbbueno: Cierto, pero estaba haciendo esto para descubrir la manera más rápida de hacer cálculos numéricos generales sobre secuencias. En el mundo real, ciertamente usaría suma a suma: D No he probado mul, pero estoy seguro de que los resultados serían similares. –

Respuesta

5

El reduce(add, r) debe invocar la función add() 100 veces , así que la pérdida de las llamadas a la función suma - reducir los usos PyEval_CallObject para invocar add en cada iteración:

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; 
    } 

Actualizado: Respuesta que hacer cola stion en los comentarios.

Al escribir 1 + 2 en el código fuente de Python, el compilador de código de bytes lleva a cabo la adición en su lugar y sustituye la expresión con 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 agrega dos variables a + b el compilador generará código de bytes que carga los dos variables y realiza una BINARY_ADD, que es mucho más rápido que llamar una función para llevar a cabo la adición:

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

¡Gracias por señalar esto! Sin embargo, no explica por qué el '1 + 2' en bruto es 5 veces más rápido que 'agregar (1,2)'. De hecho, la reducción es mucho más rápida que la de cuando se usa iadd en for. –

+0

Actualicé mi respuesta con más detalles. – samplebias

+1

¿Por qué sus ejemplos usan un paquete de terceros en lugar del módulo 'dis' incorporado? –

0

Podría ser la sobrecarga de copiar args y devolver valores (es decir, "añadir (1, 2)"), en lugar de simplemente operando en literales numéricos

0

Si reducir se utiliza para sumar NumPy ar rayos por índice, puede ser más rápido que un bucle 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)) 

Con el resultado de

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