2010-07-30 32 views
18

Ich verwende den folgenden Code, um den Test zu tun, und es scheint wie < ist langsamer als> =., Weiß jemand, warum?Warum <ist langsamer als> =

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

Sie sollten Zeit> 1000 Berechnungen und dann teilen Sie Ihre Zeit mit der Menge der Berechnungen, um eine genaue Lesung zu erhalten. – RvdK

+0

@PoweRoy ist das nicht, was er mit Nummer = 100000 macht? – djna

+0

Ich muss zugeben, dass ich Python nicht machen kann. Ich rufe an, t.timeit nennt die Ausführung 'Nummer' mal (100000). Nvm der Kommentar. – RvdK

Antwort

32

In Python 3.1.2 ist manchmal < schneller als> =. Ich versuche, es in Disassembler zu lesen,

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   

-Code ist fast identisch, aber f1 immer Linie 15 laufen und springen bis 21, f2 laufen ist immer 15 -> 18 -> 21, so sollte die Leistung beeinträchtigt werden durch wahr/falsch in if-Anweisung statt < oder> = Problem.

+1

+1 nette Erklärung – RvdK

5

Ihr erster Test wird als wahr bewertet, der zweite als falsch. Vielleicht ergibt sich daraus eine marginal unterschiedliche Verarbeitung.

+0

Könnte "pass" eine zusätzliche Verarbeitung im Vergleich zum Stoppen erfordern? –

+4

Das Ausführen des Tests mit 'x = -5' scheint das Ergebnis umzukehren. – Seth

1

Interessant! das Ergebnis wird mehr betont, wenn Sie den Ausdruck

mit IPython vereinfachen ich sehe, dass x<=0 150 ns dauert und x<0 nimmt 320 ns - mehr als doppelt so lange

andere Vergleiche x>0 und x>=0 scheinen herum zu nehmen 300 ns zu

sogar über 1000000 Schleifen die Ergebnisse schwanken ziemlich viel obwohl

2

Ich habe es gerade in Python 3.1.2 versucht - keinen Unterschied gibt es.

EDIT: Nach vielen Versuchen mit einer höheren Anzahl von Wiederholungen, ich sehe sehr unterschiedliche Werte um den Faktor 3 für beiden Versionen:

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

Also ich denke, dass Terminüberschneidungen mit anderen Prozessen sind die Hauptvariable hier.

2

Es scheint etwas inhärenten Overhead in "Zeit" für bestimmte Aktivierungen davon (unerwartet genug).

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) 

Auf meinem Rechner ist der Ausgang (konsequent und unabhängig davon, wie viele Schleifen wir versuchen - so ist es wahrscheinlich mit etwas auf der Maschine geschieht nicht nur zufällig zusammenfallen sonst) -

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' dauert immer länger. Aber wenn Sie versuchen, die Aufrufe oder Objekterstellung neu zu ordnen, verhalten sich die Dinge anders (und nicht in einem Muster, das ich leicht erklären könnte).

Dies ist keine Antwort auf Ihre Frage, nur eine Beobachtung, dass Messungen auf diese Weise inhärente Ungenauigkeiten haben können.

5

Der COMPARE_OP Opcode enthält eine Optimierung für den Fall, dass beiden Operanden ganze Zahlen sind kompatibel mit C und in diesem Fall tut es nur den Vergleich inline mit einer switch-Anweisung auf der Art des Vergleichs:

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

So ist die nur Variationen, die Sie im Vergleich haben können, sind der Weg durch die switch-Anweisung und ob das Ergebnis Wahr oder Falsch ist.Meine Vermutung wäre, dass Sie nur Variationen aufgrund des Ausführungspfades der CPU (und vielleicht der Verzweigungsvorhersage) sehen, so dass der Effekt, den Sie sehen, ebenso einfach verschwinden oder umgekehrt in anderen Versionen von Python sein könnte.

1

Das war eine ziemlich interessante Frage. Ich entfernte die if cond: pass, indem ich stattdessen verwendete, aber es beseitigte den Unterschied nicht vollständig. Ich bin noch nicht sicher, die Antwort, aber ich fand einen plausiblen Grund:

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

Dies ist von Objekten/object.c funcion convert_3way_to_object. Beachten Sie, dass> = der letzte Zweig ist; das bedeutet, dass es alleine keinen Ausgangssprung benötigt. Diese Break-Anweisung ist eliminiert. Es entspricht der Demontage von 0 und 5 in shiki. Da es sich um eine unbedingte Unterbrechung handelt, kann es durch eine Verzweigungsvorhersage gehandhabt werden, es kann jedoch auch dazu führen, dass weniger Code geladen wird.

Auf dieser Ebene wird der Unterschied natürlich sehr maschinenspezifisch sein. Meine Messungen sind nicht sehr gründlich, aber das war der einzige Punkt auf C-Ebene. Ich sah eine Verzerrung zwischen den Betreibern. Ich habe wahrscheinlich eine größere Abweichung von der CPU-Geschwindigkeitsskalierung bekommen.

Verwandte Themen