>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
Funktioniert auch für Tupel mit mehreren Elementen. Beide Versionen scheinen linear zu wachsen:
>>> timeit.timeit("'x' in ('x', 'y')")
0.04866674801541748
>>> timeit.timeit("'x' == 'x' or 'x' == 'y'")
0.06565782838087131
>>> timeit.timeit("'x' in ('y', 'x')")
0.08975995576448526
>>> timeit.timeit("'x' == 'y' or 'x' == 'y'")
0.12992391047427532
Aus diesem Grund denke ich, ich sollte total anfangen, in
überall zu verwenden, anstatt ==
!
python
performance
python-3.x
python-internals
Markus Meskanen
quelle
quelle
in
überall statt==
. Es ist eine vorzeitige Optimierung, die die Lesbarkeit beeinträchtigt.x ="!foo"
x in ("!foo",)
undx == "!foo"
in
anstelle von==
zu C wechselnAntworten:
Wie ich David Wolever gegenüber erwähnte, steckt mehr dahinter, als man denkt. beide Methoden versenden an
is
; Sie können dies beweisen, indem Sie dies tunDer erste kann nur so schnell sein, weil er nach Identität prüft.
Um herauszufinden, warum einer länger dauert als der andere, verfolgen wir die Ausführung.
Sie beginnen beide in
ceval.c
,COMPARE_OP
da dies der Bytecode istDadurch werden die Werte aus dem Stapel entfernt (technisch gesehen wird nur einer angezeigt).
und führt den Vergleich aus:
cmp_outcome
ist das:Hier teilen sich die Pfade. Die
PyCmp_IN
Niederlassung tutBeachten Sie, dass ein Tupel definiert ist als
Also der Zweig
wird genommen und
*sqm->sq_contains
, was die Funktion ist(objobjproc)tuplecontains
, wird genommen.Das macht
... Warten Sie, war es nicht das,
PyObject_RichCompareBool
was der andere Zweig genommen hat? Nein, das warPyObject_RichCompare
.Dieser Codepfad war kurz, daher kommt es wahrscheinlich nur auf die Geschwindigkeit dieser beiden an. Lass uns vergleichen.
Der Codepfad in wird so
PyObject_RichCompareBool
ziemlich sofort beendet. DennPyObject_RichCompare
das tut esDie
Py_EnterRecursiveCall
/Py_LeaveRecursiveCall
Combo werden nicht im vorherigen Pfad verwendet, aber dies sind relativ schnelle Makros, die nach dem Inkrementieren und Dekrementieren einiger Globals kurzgeschlossen werden.do_richcompare
tut:Dies hat einige schnelle Kontrolle zu Anruf ,
v->ob_type->tp_richcompare
der istwas tut
Diese Verknüpfungen werden nämlich aktiviert
left == right
... aber erst danachAlles in allem sehen die Pfade dann ungefähr so aus (manuelles rekursives Inlinen, Abrollen und Beschneiden bekannter Zweige)
vs.
Nun,
PyUnicode_Check
undPyUnicode_READY
sind ziemlich billig, da sie nur ein paar Felder prüfen, aber es sollte offensichtlich sein, dass das oberste ein kleinerer Codepfad ist, weniger Funktionsaufrufe, nur eine switch-Anweisung und nur etwas dünner ist.TL; DR:
Beide versenden an
if (left_pointer == right_pointer)
; Der Unterschied ist nur, wie viel Arbeit sie tun, um dorthin zu gelangen.in
macht einfach weniger.quelle
Hier spielen drei Faktoren eine Rolle, die zusammen dieses überraschende Verhalten hervorrufen.
Erstens: Der
in
Operator nimmt eine Verknüpfung und überprüft die Identität (x is y
), bevor er die Gleichheit überprüft (x == y
):Zweitens: Aufgrund der Internierung von Pythons Zeichenfolgen sind beide
"x"
s in"x" in ("x", )
identisch:(große Warnung: Diese ist implementierungsspezifisches Verhalten!
is
sollte nie Strings verwendet werden , um zu vergleichen , denn es wird manchmal überraschende Antworten geben, zum Beispiel"x" * 100 is "x" * 100 ==> False
)Drittens: wie in detailliert Veedrac fantastischen Antwort ,
tuple.__contains__
(x in (y, )
ist in etwa äquivalent(y, ).__contains__(x)
) auf den Punkt , die Identitätsprüfung schneller als die Durchführungstr.__eq__
(wiederumx == y
ist in etwa gleichwertigx.__eq__(y)
) den Fall ist.Sie können Beweise dafür sehen, weil sie
x in (y, )
erheblich langsamer sind als das logisch Äquivalentx == y
:Der
x in (y, )
Fall ist langsamer, dais
derin
Bediener nach dem Fehlschlagen des Vergleichs auf die normale Gleichheitsprüfung zurückgreift (dh mit==
), sodass der Vergleich ungefähr genauso lange dauert wie==
der Vorgang, wodurch der gesamte Vorgang aufgrund des Overheads beim Erstellen des Tupels langsamer wird , zu Fuß seine Mitglieder, etc.Beachten Sie auch , dass
a in (b, )
ist nur schneller , wenna is b
:(Warum ist
a in (b, )
schneller alsa is b or a == b
? Meine Vermutung wären weniger Anweisungen für virtuelle Maschinen -a in (b, )
es sind nur ~ 3 Anweisungen, woa is b or a == b
einige VM-Anweisungen mehr vorhanden sind.)Die Antwort von Veedrac - https://stackoverflow.com/a/28889838/71522 - geht viel detaillierter darauf ein, was genau während
==
und passiert,in
und ist es wert, gelesen zu werden.quelle
X in [X,Y,Z]
zu arbeiten korrekt , ohneX
,Y
oderZ
mit Gleichheit Methoden definieren (oder besser gesagt, die Standard - Gleichheit istis
, so dass es mit spart rief__eq__
auf Objekte ohne benutzerdefinierten__eq__
undis
wahr sein sollte Wert implizieren -Gleichberechtigung).float('nan')
ist möglicherweise irreführend. Es ist eine Eigenschaft davon,nan
dass es nicht gleich sich selbst ist. Das kann das Timing ändern.in
für Mitgliedschaftstests veranschaulichen . Ich werde den Variablennamen ändern, um dies zu verdeutlichen.tuple.__contains__
ist in CPython 3.4.3 implementiert, mittuplecontains
dem AufrufePyObject_RichCompareBool
und die im Falle einer Identität sofort zurückkehren.unicode
hatPyUnicode_RichCompare
unter der Haube, die die gleiche Abkürzung für Identität hat."x" is "x"
wird nicht unbedingt so seinTrue
.'x' in ('x', )
wird immer seinTrue
, aber es scheint nicht schneller zu sein als==
.