Von dieser Seite wissen wir, dass:
Verkettete Vergleiche sind schneller als mit dem
and
Operator. Schreibenx < y < z
stattx < y and y < z
.
Ich habe jedoch ein anderes Ergebnis beim Testen der folgenden Codefragmente erhalten:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
Es scheint, dass x < y and y < z
das schneller ist als x < y < z
. Warum?
Nachdem ich einige Beiträge auf dieser Site durchsucht habe (wie diese hier ), weiß ich, dass "nur einmal bewertet" der Schlüssel ist x < y < z
, aber ich bin immer noch verwirrt. Um weitere Studien durchzuführen, habe ich diese beiden Funktionen mit dis.dis
folgenden Komponenten zerlegt :
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
Und die Ausgabe ist:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
Es scheint, dass der x < y and y < z
Befehl weniger uneinheitlich ist als x < y < z
. Sollte ich x < y and y < z
schneller überlegen als x < y < z
?
Getestet mit Python 2.7.6 auf einer Intel (R) Xeon (R) CPU E5640 bei 2,67 GHz.
quelle
timeit
Tests sah, interessierte ich mich dafür.y
es sich nicht nur um eine variable Suche handelt, sondern um einen teureren Prozess wie einen Funktionsaufruf. Dh10 < max(range(100)) < 15
ist schneller als10 < max(range(100)) and max(range(100)) < 15
weilmax(range(100))
für beide Vergleiche einmal aufgerufen wird.Antworten:
Der Unterschied besteht darin, dass in
x < y < z
y
nur einmal ausgewertet wird. Dies macht keinen großen Unterschied, wenn y eine Variable ist, aber es ist, wenn es sich um einen Funktionsaufruf handelt, dessen Berechnung einige Zeit in Anspruch nimmt.quelle
sleep()
Insider-Funktion?Optimaler Bytecode für beide von Ihnen definierten Funktionen wäre
weil das Ergebnis des Vergleichs nicht verwendet wird. Machen wir die Situation interessanter, indem wir das Ergebnis des Vergleichs zurückgeben. Lassen Sie uns auch das Ergebnis zur Kompilierungszeit nicht erkennbar machen.
Auch hier sind die beiden Versionen des Vergleichs semantisch identisch, sodass der optimale Bytecode für beide Konstrukte gleich ist. So gut ich es herausfinden kann, würde es so aussehen. Ich habe jede Zeile vor und nach jedem Opcode in der Forth-Notation mit dem Stapelinhalt versehen (oben auf dem Stapel rechts,
--
vor und nach geteilt, nachfolgend?
zeigt etwas an, das möglicherweise vorhanden ist oder nicht). Beachten Sie, dassRETURN_VALUE
alles, was sich auf dem Stapel befindet, unter dem zurückgegebenen Wert verworfen wird.Wenn eine Implementierung der Sprache CPython, PyPy, was auch immer, diesen Bytecode (oder seine eigene äquivalente Abfolge von Operationen) nicht für beide Variationen generiert , zeigt dies die schlechte Qualität dieses Bytecode-Compilers . Das Abrufen der Bytecode-Sequenzen, die Sie oben gepostet haben, ist ein gelöstes Problem (ich denke, alles, was Sie für diesen Fall benötigen, ist ständiges Falten , Eliminierung von totem Code und bessere Modellierung des Inhalts des Stapels; die Eliminierung von allgemeinen Unterausdrücken wäre ebenfalls billig und wertvoll ), und es gibt wirklich keine Entschuldigung dafür, dies in einer modernen Sprachimplementierung nicht zu tun.
Nun kommt es vor, dass alle aktuellen Implementierungen der Sprache Bytecode-Compiler von schlechter Qualität haben. Aber das sollten Sie beim Codieren ignorieren ! Stellen Sie sich vor, der Bytecode-Compiler sei gut und schreiben Sie den am besten lesbaren Code. Es wird wahrscheinlich sowieso schnell genug sein. Wenn dies nicht der Fall ist, suchen Sie zuerst nach algorithmischen Verbesserungen und versuchen Sie es dann mit Cython. Dies bietet bei gleichem Aufwand weitaus mehr Verbesserungen als alle Optimierungen auf Ausdrucksebene, die Sie möglicherweise anwenden.
quelle
interesting_compare
einfachen Bytecode oben optimieren (der nur mit Inlining funktionieren würde). Völlig offtopic aber: Inlining ist eine der wichtigsten Optimierungen für jeden Compiler. Sie können versuchen, einige Benchmarks mit beispielsweise HotSpot für echte Programme auszuführen (nicht einige Mathe-Tests, die 99% ihrer Zeit in einer von Hand optimierten Hot-Schleife verbringen [und daher sowieso keine Funktionsaufrufe mehr haben]), wenn Sie diese deaktivieren Fähigkeit, alles zu inline - Sie werden große Regressionen sehen.interesting_compare
.y
den Stack nicht ändern, da es viele Debug-Tools enthält.Da der Unterschied in der Ausgabe auf mangelnde Optimierung zurückzuführen zu sein scheint, sollten Sie diesen Unterschied in den meisten Fällen ignorieren - es könnte sein, dass der Unterschied verschwindet. Der Unterschied besteht darin, dass
y
nur einmal ausgewertet werden sollte und dies durch Duplizieren auf dem Stapel gelöst wird, was eine zusätzlichePOP_TOP
Lösung erfordertLOAD_FAST
könnte jedoch möglich sein.Der wichtige Unterschied ist jedoch, dass in
x<y and y<z
der zweiteny
zweimalx<y
bewertet werden sollte, wenn als wahr bewertet wird, dies hat Auswirkungen, wenn die Bewertung vony
viel Zeit nimmt oder Nebenwirkungen hat.In den meisten Szenarien sollten Sie verwenden,
x<y<z
obwohl es etwas langsamer ist.quelle
Erstens ist Ihr Vergleich so gut wie bedeutungslos, da die beiden verschiedenen Konstrukte nicht eingeführt wurden, um eine Leistungsverbesserung zu erzielen. Sie sollten sich daher nicht entscheiden, ob Sie eines anstelle des anderen verwenden möchten.
Das
x < y < z
Konstrukt:x
,y
undz
einmal und prüfen , ob der gesamte Zustand hält. Durchand
die Verwendung wird die Semantik durchy
mehrmaliges Auswerten geändert , wodurch sich das Ergebnis ändern kann .Wählen Sie also eine anstelle der anderen, abhängig von der gewünschten Semantik und, wenn sie gleichwertig sind, ob eine besser lesbar ist als die andere.
Dies bedeutet: Mehr zerlegter Code bedeutet nicht langsameren Code. Das Ausführen von mehr Bytecode-Operationen bedeutet jedoch, dass jede Operation einfacher ist und dennoch eine Iteration der Hauptschleife erfordert. Dies bedeutet, dass der Aufwand für die Ausführung weiterer Bytecode-Operationen von Bedeutung sein kann , wenn die von Ihnen ausgeführten Vorgänge extrem schnell sind (z. B. die Suche nach lokalen Variablen, wie Sie sie dort ausführen).
Beachten Sie jedoch, dass dieses Ergebnis nicht für die allgemeinere Situation gilt, sondern nur für den "schlimmsten Fall", den Sie zufällig profilieren. Wie andere angemerkt haben, wenn Sie sich ändern
y
die Ergebnisse ändern zu etwas , das noch etwas länger dauert, da die verkettete Notation sie nur einmal auswertet.Zusammenfassend:
quelle