2010-07-30 9 views
18

Estoy usando el siguiente código para hacer la prueba y parece que < es más lento que> =., ¿Alguien sabe por qué?Por qué <es más lento que> =

import timeit 
s = """ 
    x=5 
    if x<0: pass 
""" 
    t = timeit.Timer(stmt=s) 
    print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000) 
#0.21 usec/pass 
z = """ 
    x=5 
    if x>=0: pass 
""" 
t2 = timeit.Timer(stmt=z) 
print "%.2f usec/pass" % (1000000 * t2.timeit(number=100000)/100000) 
#0.18 usec/pass 
+0

Debe medir> 1000 cálculos y luego dedique su tiempo con la cantidad de cálculos para obtener una lectura precisa. – RvdK

+0

@PoweRoy ¿no es eso lo que está haciendo con number = 100000? – djna

+0

Tengo que admitir que no puedo hacer Python. Supongo que llamar a t.timeit hace los tiempos de 'número' de ejecución (100000). Nvm el comentario. – RvdK

Respuesta

32

En Python 3.1.2, en algún momento < es más rápido que> =. Trato de leer en desensamblador,

import dis 
def f1(): 
    x=5 
    if x < 0: pass 

def f2(): 
    x = 5 
    if x >=0: pass 

>>> dis.dis(f1) 
    2   0 LOAD_CONST    1 (5) 
       3 STORE_FAST    0 (x) 

    3   6 LOAD_FAST    0 (x) 
       9 LOAD_CONST    2 (0) 
      12 COMPARE_OP    0 (<) 
      15 POP_JUMP_IF_FALSE  21 
      18 JUMP_FORWARD    0 (to 21) 
     >> 21 LOAD_CONST    0 (None) 
      24 RETURN_VALUE   
>>> dis.dis(f2) 
    2   0 LOAD_CONST    1 (5) 
       3 STORE_FAST    0 (x) 

    3   6 LOAD_FAST    0 (x) 
       9 LOAD_CONST    2 (0) 
      12 COMPARE_OP    5 (>=) 
      15 POP_JUMP_IF_FALSE  21 
      18 JUMP_FORWARD    0 (to 21) 
     >> 21 LOAD_CONST    0 (None) 
      24 RETURN_VALUE   

Código es casi idéntica, pero f1 se aplica siempre en la línea 15 y saltar a 21, f2 es siempre correr 15 -> 18 -> 21, por lo que el desempeño se ve afectado por verdadero/falso en la declaración if en lugar de < o> = problema.

+1

+1 bonita explicación – RvdK

5

Su primera prueba se evalúa como verdadera, la segunda como falsa. Tal vez haya un procesamiento marginalmente diferente como resultado.

+0

¿Podría 'pasar' tomar procesamiento adicional en comparación con simplemente detenerse? –

+4

Al ejecutar la prueba con 'x = -5' parece invertir el resultado. – Seth

1

¡Interesante! El resultado es más destacada si se simplifica la expresión

usando IPython veo que x<=0 toma 150ns y x<0 toma 320ns - más de dos veces más largo

otras comparaciones x>0 y x>=0 parecen tomar alrededor de 300 ns demasiado

incluso más de 1000000 bucles los resultados fluctúan bastante aunque

2

Lo he intentado en Python 3.1.2 - no hay diferencia.

EDIT: Después de muchos intentos con un mayor número de repeticiones, estoy viendo los valores que varían tremendamente en un factor de 3 para las dos versiones:

>>> import timeit 
>>> s = """x=5 
... if x<0: pass""" 
>>> t = timeit.Timer(stmt=s) 
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000)) 
1.48 usec/pass 
>>> 
>>> z = """x=5 
... if x>=0: pass""" 
>>> t2 = timeit.Timer(stmt=z) 
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000)) 
0.59 usec/pass 
>>> 
>>> import timeit 
>>> s = """x=5 
... if x<0: pass""" 
>>> t = timeit.Timer(stmt=s) 
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000)) 
0.57 usec/pass 
>>> 
>>> z = """x=5 
... if x>=0: pass""" 
>>> t2 = timeit.Timer(stmt=z) 
>>> print ("%.2f usec/pass" % (1000000 * t.timeit(number=1000000)/100000)) 
1.47 usec/pass 

así que supongo que conflictos de programación con otros procesos son la variable principal aquí.

2

Parece haber una sobrecarga inherente en 'timeit' para ciertas activaciones de la misma (lo suficientemente inesperado).

Try -

import timeit 

Times = 30000000 

s = """ 
    x=5 
    if x>=0: pass 
""" 

t1 = timeit.Timer(stmt=s) 
t2 = timeit.Timer(stmt=s) 
t3 = timeit.Timer(stmt=s) 

print t1.timeit(number=Times) 
print t2.timeit(number=Times) 
print t3.timeit(number=Times) 
print t1.timeit(number=Times) 
print t2.timeit(number=Times) 
print t3.timeit(number=Times) 
print t1.timeit(number=Times) 
print t2.timeit(number=Times) 
print t3.timeit(number=Times) 
print t1.timeit(number=Times) 
print t2.timeit(number=Times) 
print t3.timeit(number=Times) 

En mi máquina es la salida (consistente, y con independencia de la cantidad de bucles que intento - por lo que probablemente no sólo sucede para coincidir con otra cosa que sucede en la máquina) -

1.96510925271 
1.84014169399 
1.84004224001 
1.97851123537 
1.86845451028 
1.83624929984 
1.94599509155 
1.85690220405 
1.8338135154 
1.98382475985 
1.86861430713 
1.86006657271 

't1' siempre lleva más tiempo. Pero si intenta reordenar las llamadas o la creación del objeto, las cosas se comportarán de manera diferente (y no en un patrón que pueda explicar fácilmente).

Esta no es una respuesta a su pregunta, solo una observación de que medir de esta manera puede tener inexactitudes inherentes.

5

El código de operación COMPARE_OP contiene una optimización para el caso en que ambos operandos son enteros compatibles con C y en ese caso sólo hace la comparación en línea con una sentencia switch en el tipo de comparación:

if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) { 
     /* INLINE: cmp(int, int) */ 
     register long a, b; 
     register int res; 
     a = PyInt_AS_LONG(v); 
     b = PyInt_AS_LONG(w); 
     switch (oparg) { 
     case PyCmp_LT: res = a < b; break; 
     case PyCmp_LE: res = a <= b; break; 
     case PyCmp_EQ: res = a == b; break; 
     case PyCmp_NE: res = a != b; break; 
     case PyCmp_GT: res = a > b; break; 
     case PyCmp_GE: res = a >= b; break; 
     case PyCmp_IS: res = v == w; break; 
     case PyCmp_IS_NOT: res = v != w; break; 
     default: goto slow_compare; 
     } 
     x = res ? Py_True : Py_False; 
     Py_INCREF(x); 
} 

Así que la solo las variaciones que puede tener en la comparación son la ruta a través de la declaración de cambio y si el resultado es verdadero o falso.Supongo que solo está viendo variaciones debido a la ruta de ejecución de la CPU (y tal vez a la predicción de bifurcación), por lo que el efecto que está viendo podría desaparecer fácilmente o ser al revés en otras versiones de Python.

1

Esta fue una pregunta bastante intrigante. Eliminé el if cond: pass usando v=cond, pero no eliminó por completo la diferencia. Todavía no estoy seguro de la respuesta, pero he encontrado una razón plausible:

switch (op) { 
    case Py_LT: c = c < 0; break; 
    case Py_LE: c = c <= 0; break; 
    case Py_EQ: c = c == 0; break; 
    case Py_NE: c = c != 0; break; 
    case Py_GT: c = c > 0; break; 
    case Py_GE: c = c >= 0; break; 
} 

Esto es de Objetos/convert_3way_to_object funcion object.c. Tenga en cuenta que> = es la última rama; eso significa que, solo, no necesita saltar de salida. Esa declaración de interrupción se elimina. Coincide con el 0 y 5 en el desmontaje de shiki. Al tratarse de una ruptura incondicional, puede ser manejada por la predicción de bifurcación, pero también puede dar lugar a que se cargue menos código.

En este nivel, la diferencia será, naturalmente, altamente específica de la máquina. Mis medidas no son muy minuciosas, pero este fue el único punto en el nivel C. Vi un sesgo entre los operadores. Probablemente recibí un mayor sesgo de la escala de velocidad de la CPU.

Cuestiones relacionadas