>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
Fonctionne également pour les tuples avec plusieurs éléments, les deux versions semblent se développer de manière linéaire:
>>> 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
Sur cette base, je pense que je devrais totalement commencer à utiliser in
partout au lieu de ==
!
python
performance
python-3.x
python-internals
Markus Meskanen
la source
la source
in
partout au lieu de==
. C'est une optimisation prématurée qui nuit à la lisibilité.x ="!foo"
x in ("!foo",)
etx == "!foo"
in
au lieu de==
consiste à passer à C.Réponses:
Comme je l'ai mentionné à David Wolever, il y a plus que cela à première vue; les deux méthodes sont envoyées à
is
; vous pouvez le prouver en faisantLe premier ne peut être aussi rapide car il vérifie par identité.
Pour savoir pourquoi l'un prendrait plus de temps que l'autre, suivons l'exécution.
Ils commencent tous les deux
ceval.c
,COMPARE_OP
puisque c'est le bytecode impliquéCela extrait les valeurs de la pile (techniquement, il n'en affiche qu'une seule)
et exécute la comparaison:
cmp_outcome
est-ce:C'est là que les chemins se séparent. La
PyCmp_IN
branche neNotez qu'un tuple est défini comme
Donc la branche
sera prise et
*sqm->sq_contains
, qui est la fonction(objobjproc)tuplecontains
, sera prise.Cela ne
... Attendez, n'est-
PyObject_RichCompareBool
ce pas ce que l'autre branche a pris? Non, c'étaitPyObject_RichCompare
.Ce chemin de code était court donc il se résume probablement à la vitesse de ces deux. Comparons.
Le chemin de code dans
PyObject_RichCompareBool
se termine presque immédiatement. CarPyObject_RichCompare
,Le combo
Py_EnterRecursiveCall
/Py_LeaveRecursiveCall
n'est pas repris dans le chemin précédent, mais ce sont des macros relativement rapides qui court-circuiteront après incrémentation et décrémentation de certains globaux.do_richcompare
Est-ce que:Cela fait quelques vérifications rapides pour appeler
v->ob_type->tp_richcompare
ce qui estqui fait
A savoir, ces raccourcis sur
left == right
... mais seulement après avoir faitDans l'ensemble, les chemins ressemblent alors à quelque chose comme cela (alignement récursif manuel, déroulement et élagage des branches connues)
contre
Maintenant,
PyUnicode_Check
etPyUnicode_READY
sont assez bon marché car ils ne vérifient que quelques champs, mais il devrait être évident que celui du haut est un chemin de code plus petit, il a moins d'appels de fonction, une seule instruction switch et est juste un peu plus mince.TL; DR:
Les deux expédient à
if (left_pointer == right_pointer)
; la différence est juste combien de travail ils font pour y arriver.in
fait juste moins.la source
Il y a trois facteurs en jeu ici qui, combinés, produisent ce comportement surprenant.
Premièrement: l'
in
opérateur prend un raccourci et vérifie l'identité (x is y
) avant de vérifier l'égalité (x == y
):Deuxièmement: en raison de l'internement de chaînes de Python, les deux
"x"
s dans"x" in ("x", )
seront identiques:(grand avertissement: ce comportement est spécifique, de mise en œuvre!
is
doit jamais être utilisé pour comparer des chaînes , car il va donner des réponses surprenantes parfois, par exemple"x" * 100 is "x" * 100 ==> False
)Troisièmement: comme détaillé dans la réponse fantastique de Veedrac ,
tuple.__contains__
(x in (y, )
est à peu près équivalent à(y, ).__contains__(x)
) arrive au point d'effectuer le contrôle d'identité plus rapidement questr.__eq__
(encore une fois,x == y
est à peu près équivalent àx.__eq__(y)
).Vous pouvez en voir la preuve car il
x in (y, )
est beaucoup plus lent que son équivalent logiquex == y
:Le
x in (y, )
cas est plus lent car, après l'is
échec de la comparaison, l'in
opérateur revient à la vérification d'égalité normale (c'est-à-dire à l'aide==
), de sorte que la comparaison prend environ le même temps que==
, ce qui rend toute l'opération plus lente en raison de la surcharge de création du tuple , promener ses membres, etc.Notez également que ce
a in (b, )
n'est plus rapide que lorsquea is b
:(pourquoi est
a in (b, )
plus rapide quea is b or a == b
? Je suppose qu'il y aurait moins d'instructions de machine virtuelle - ila in (b, )
n'y a que 3 instructions, où il ya is b or a == b
aura pas mal d'autres instructions de machine virtuelle)La réponse de Veedrac - https://stackoverflow.com/a/28889838/71522 - va beaucoup plus de détails sur ce qui se passe précisément au cours de chacune
==
etin
et vaut bien la lecture.la source
X in [X,Y,Z]
de fonctionner correctement sansX
,Y
ou d'Z
avoir à définir des méthodes d'égalité (ou plutôt, l'égalité par défaut estis
, donc cela évite d'avoir à appeler des__eq__
objets sans définition utilisateur__eq__
etis
être vrai devrait impliquer une valeur -égalité).float('nan')
est potentiellement trompeuse. C'est une propriété denan
ce qu'il n'est pas égal à lui-même. Cela peut changer le timing.in
des tests d'adhésion. Je vais changer le nom de la variable pour clarifier.tuple.__contains__
est mis en œuvre partuplecontains
lequel les appelsPyObject_RichCompareBool
et qui revient immédiatement en cas d'identité.unicode
aPyUnicode_RichCompare
sous le capot, qui a le même raccourci pour l'identité."x" is "x"
ne sera pas nécessairement le casTrue
.'x' in ('x', )
sera toujoursTrue
, mais il peut ne pas sembler être plus rapide que==
.