2010-07-30 17 views
18

Sto usando il seguente codice per fare il test e sembra che < sia più lento di> =., Qualcuno sa perché?Perché <è più lento di> =

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

È necessario tempo> 1000 calcoli e quindi dividere il tempo con la quantità di calcoli per ottenere una lettura accurata. – RvdK

+0

@PoweRoy non è quello che sta facendo con number = 100000? – djna

+0

Devo ammettere che non posso fare Python. Suppongo che chiamare t.timeit esegua l'operazione 'numero' volte (100000). Nvm il commento. – RvdK

risposta

32

In Python 3.1.2, a volte è più veloce di> =. Cerco di leggere in disassembler,

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   

codice è quasi identico, ma la F1 è sempre correre linea 15 e saltate per 21, f2 è sempre eseguito 15 -> 18 -> 21, in modo che le prestazioni dovrebbero essere influenzata da vero/falso in istruzione if anziché < o> = problema.

+1

+1 bella spiegazione – RvdK

5

Il tuo primo test è true, il secondo falso. Forse c'è un'elaborazione marginalmente diversa come risultato.

+0

Potrebbe 'pass' prendere l'elaborazione in più rispetto all'arresto? –

+4

L'esecuzione del test con 'x = -5' sembra invertire il risultato. – Seth

1

Interessante! il risultato è più accentuato se si semplifica l'espressione

utilizzando IPython vedo che x<=0 prende 150ns e x<0 prende 320ns - oltre il doppio del tempo

altri confronti x>0 e x>=0 sembrano prendere in giro 300ns troppo

anche oltre 1000000 cicli i risultati fluttuano parecchio anche se

2

Ho appena provato in Python 3.1.2 - nessuna differenza lì.

EDIT: Dopo molti tentativi con un più alto numero di ripetizioni, sto vedendo valori selvaggiamente variano di un fattore 3 per entrambe le versioni:

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

quindi credo che i conflitti di programmazione con altri processi sono il variabile principale qui.

2

Sembra esserci un sovraccarico inerente a "timeit" per alcune attivazioni di esso (in modo imprevisto).

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) 

Sulla mia macchina l'uscita è (in modo coerente, e indipendentemente dal numero di cicli provo - in modo che probabilmente non capita in coincidenza con qualcos'altro accadendo sulla macchina) -

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' richiede sempre più tempo. Ma se si tenta di riordinare le chiamate o la creazione dell'oggetto, le cose si comportano diversamente (e non in un modello che potrei facilmente spiegare).

Questa non è una risposta alla tua domanda, solo un'osservazione che la misurazione in questo modo potrebbe avere inesattezze intrinseche.

5

Il codice operativo COMPARE_OP contiene un'ottimizzazione per il caso in cui entrambi gli operandi sono interi compatibili con C e in questo caso lo fa solo il confronto in linea con un'istruzione switch sul tipo di confronto:

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

Così il solo le varianti che puoi avere nel confronto sono il percorso attraverso l'istruzione switch e se il risultato è True o False.La mia ipotesi sarebbe che si vedano solo variazioni dovute al percorso di esecuzione della CPU (e forse alla previsione di branch), quindi l'effetto che si sta vedendo potrebbe altrettanto facilmente scomparire o essere il contrario in altre versioni di Python.

1

Questa era una domanda piuttosto intrigante. Ho rimosso il if cond: pass utilizzando v=cond, ma non ha eliminato completamente la differenza. Non sono ancora certo della risposta, ma ho trovato una ragione plausibile:

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

Questo è da oggetti/object.c convert_3way_to_object funcion. Si noti che> = è l'ultimo ramo; questo significa che, da solo, non ha bisogno di saltare l'uscita. Questa affermazione di rottura è stata eliminata. Si abbina con lo 0 e lo 5 nello smontaggio di shiki. Essendo un'interruzione incondizionata, può essere gestita dalla previsione del ramo, ma può anche comportare meno codice da caricare.

A questo livello, la differenza sarà naturalmente molto specifica della macchina. Le mie misurazioni non sono molto accurate, ma questo è stato l'unico punto a livello C che ho visto un pregiudizio tra gli operatori. Probabilmente ho avuto un maggiore pregiudizio dal ridimensionamento della velocità della CPU.