Dies ist eine Folgefrage zu einer Antwort, die ich vor ein paar Tagen gegeben habe . Bearbeiten: Es scheint, dass das OP dieser Frage bereits den Code verwendet hat, den ich an ihn gesendet habe, um dieselbe Frage zu stellen , aber ich war mir dessen nicht bewusst. Entschuldigung. Die Antworten sind jedoch unterschiedlich!
Im Wesentlichen habe ich Folgendes beobachtet:
>>> def without_else(param=False):
... if param:
... return 1
... return 0
>>> def with_else(param=False):
... if param:
... return 1
... else:
... return 0
>>> from timeit import Timer as T
>>> T(lambda : without_else()).repeat()
[0.3011460304260254, 0.2866089344024658, 0.2871549129486084]
>>> T(lambda : with_else()).repeat()
[0.27536892890930176, 0.2693932056427002, 0.27011704444885254]
>>> T(lambda : without_else(True)).repeat()
[0.3383951187133789, 0.32756996154785156, 0.3279120922088623]
>>> T(lambda : with_else(True)).repeat()
[0.3305950164794922, 0.32186388969421387, 0.3209099769592285]
... oder mit anderen Worten: Die else
Klausel ist schneller, unabhängig davon, welche if
Bedingung ausgelöst wird oder nicht.
Ich nehme an, es hat mit dem unterschiedlichen Bytecode zu tun, der von den beiden generiert wurde, aber kann jemand dies im Detail bestätigen / erklären?
BEARBEITEN: Es scheint, dass nicht jeder in der Lage ist, meine Timings zu reproduzieren. Daher hielt ich es für nützlich, einige Informationen zu meinem System anzugeben. Ich verwende Ubuntu 11.10 64-Bit mit dem installierten Standard-Python. python
generiert die folgenden Versionsinformationen:
Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Hier sind die Ergebnisse der Demontage in Python 2.7:
>>> dis.dis(without_else)
2 0 LOAD_FAST 0 (param)
3 POP_JUMP_IF_FALSE 10
3 6 LOAD_CONST 1 (1)
9 RETURN_VALUE
4 >> 10 LOAD_CONST 2 (0)
13 RETURN_VALUE
>>> dis.dis(with_else)
2 0 LOAD_FAST 0 (param)
3 POP_JUMP_IF_FALSE 10
3 6 LOAD_CONST 1 (1)
9 RETURN_VALUE
5 >> 10 LOAD_CONST 2 (0)
13 RETURN_VALUE
14 LOAD_CONST 0 (None)
17 RETURN_VALUE
LOAD_CONST(None); RETURN_VALUE
- aber wie gesagt, er wird nie erreicht) am Ende vonwith_else
. Ich bezweifle sehr, dass toter Code eine Funktion schneller macht. Könnte jemand einedis
auf 2.7 zur Verfügung stellen?else
undFalse
war am langsamsten von allen (152ns). Die zweitschnellste warTrue
ohneelse
(143 ns) und zwei andere waren im Grunde gleich (137 ns und 138 ns). Ich habe keinen Standardparameter verwendet und ihn%timeit
in iPython gemessen .with_else
spürbar schneller ist.Antworten:
Dies ist eine reine Vermutung, und ich habe keinen einfachen Weg gefunden, um zu überprüfen, ob es richtig ist, aber ich habe eine Theorie für Sie.
Ich habe Ihren Code ausprobiert und erhalte die gleichen Ergebnisse,
without_else()
ist immer wieder etwas langsamer alswith_else()
:Wenn man bedenkt, dass der Bytecode identisch ist, ist der einzige Unterschied der Name der Funktion. Insbesondere führt der Timing-Test eine Suche nach dem globalen Namen durch. Versuchen Sie es umzubenennen
without_else()
und der Unterschied verschwindet:Ich vermute das
without_else
es eine Hash-Kollision mit etwas anderem gibt,globals()
sodass die Suche nach globalen Namen etwas langsamer ist.Bearbeiten : Ein Wörterbuch mit 7 oder 8 Schlüsseln hat wahrscheinlich 32 Steckplätze, daher hat es auf dieser Basis
without_else
eine Hash-Kollision mit__builtins__
:Um zu verdeutlichen, wie das Hashing funktioniert:
__builtins__
Hashes auf -1196389688, wodurch die Tabellengröße (32) modulo reduziert wurde, bedeutet, dass sie im Steckplatz Nr. 8 der Tabelle gespeichert ist.without_else
Hashes auf 505688136, wodurch Modulo 32 auf 8 reduziert wurde, sodass es zu einer Kollision kommt. Um dies zu beheben, berechnet Python:Beginnen mit:
Wiederholen Sie diesen Vorgang, bis wir einen freien Platz gefunden haben:
das gibt es 17 als nächsten Index zu verwenden. Zum Glück ist das kostenlos, so dass sich die Schleife nur einmal wiederholt. Die Größe der Hash-Tabelle ist eine Potenz von 2, ebenso
2**i
wie die Größe der Hash-Tabellei
die Anzahl der aus dem Hash-Wert verwendeten Bitsj
.Jede Sonde in der Tabelle kann eine davon finden:
Der Steckplatz ist leer. In diesem Fall stoppt die Prüfung und wir wissen, dass der Wert nicht in der Tabelle enthalten ist.
Der Slot wird nicht verwendet, wurde aber in der Vergangenheit verwendet. In diesem Fall versuchen wir es mit dem nächsten wie oben berechneten Wert.
Der Slot ist voll, aber der in der Tabelle gespeicherte vollständige Hashwert stimmt nicht mit dem Hash des gesuchten Schlüssels überein (dies geschieht im Fall von
__builtins__
vswithout_else
).Der Slot ist voll und hat genau den gewünschten Hash-Wert. Dann prüft Python, ob der Schlüssel und das Objekt, nach dem wir suchen, dasselbe Objekt sind (in diesem Fall, weil kurze Zeichenfolgen, die Bezeichner sein könnten, so interniert sind identische Bezeichner verwenden genau dieselbe Zeichenfolge).
Wenn der Slot voll ist, stimmt der Hash genau überein, aber die Schlüssel sind nicht das gleiche Objekt. Dann und nur dann wird Python versuchen, sie auf Gleichheit zu vergleichen. Dies ist vergleichsweise langsam, sollte aber bei Namenssuchen eigentlich nicht passieren.
quelle