>>> timeit.timeit("'x' in ('x',)")
0.04869917374131205
>>> timeit.timeit("'x' == 'x'")
0.06144205736110564
También funciona para tuplas con múltiples elementos, ambas versiones parecen crecer linealmente:
>>> 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
En base a esto, creo que debería totalmente empezar a utilizar intodas partes en lugar de ==!
python
performance
python-3.x
python-internals
Markus Meskanen
fuente
fuente

intodas partes en lugar de==. Es una optimización prematura que perjudica la legibilidad.x ="!foo"x in ("!foo",)yx == "!foo"inlugar de usarlo==es cambiar a C.Respuestas:
Como le mencioné a David Wolever, hay más en esto de lo que parece; ambos métodos se envían a
is; puedes probar esto haciendoEl primero solo puede ser tan rápido porque verifica por identidad.
Para descubrir por qué uno tomaría más tiempo que el otro, analicemos la ejecución.
Ambos comienzan en
ceval.c,COMPARE_OPya que ese es el bytecode involucradoEsto muestra los valores de la pila (técnicamente solo muestra uno)
y ejecuta la comparación:
cmp_outcomeEs esto:Aquí es donde se dividen los caminos. La
PyCmp_INrama haceTenga en cuenta que una tupla se define como
Entonces la rama
será tomada y
*sqm->sq_contains, cuál es la función(objobjproc)tuplecontains, será tomada.Esto hace
... Espera, ¿no fue eso
PyObject_RichCompareBoollo que tomó la otra rama? No, eso fuePyObject_RichCompare.Esa ruta de código era corta, por lo que probablemente solo se reduzca a la velocidad de estos dos. Comparemos.
La ruta del código en
PyObject_RichCompareBoolcasi termina inmediatamente. ParaPyObject_RichCompare, lo haceEl
Py_EnterRecursiveCall/Py_LeaveRecursiveCallcombo no se toma en la ruta anterior, pero estas son macros relativamente rápidas que cortocircuitarán después de aumentar y disminuir algunas variables globales.do_richcomparehace:Esto hace algunas verificaciones rápidas para llamar,
v->ob_type->tp_richcompareque esque hace
A saber, estos atajos en
left == right... pero solo después de hacerDespués de todo, todos los caminos se ven así (reclinando, desenrollando y podando manualmente ramas conocidas)
vs
Ahora,
PyUnicode_CheckyPyUnicode_READYson bastante baratos, ya que solo verifican un par de campos, pero debería ser obvio que el superior es una ruta de código más pequeña, tiene menos llamadas a funciones, solo una declaración de interruptor y es un poco más delgada.TL; DR:
Ambos despachan a
if (left_pointer == right_pointer); la diferencia es cuánto trabajo hacen para llegar allí.insolo hace menos.fuente
Aquí hay tres factores en juego que, combinados, producen este comportamiento sorprendente.
Primero: el
inoperador toma un atajo y verifica la identidad (x is y) antes de verificar la igualdad (x == y):Segundo: debido al internamiento de la cadena de Python, ambos
"x"s"x" in ("x", )serán idénticos:(gran advertencia: este es un comportamiento aplicación específica!
isdebe nunca se puede utilizar para comparar cadenas, ya que va a dar respuestas sorprendentes a veces, por ejemplo"x" * 100 is "x" * 100 ==> False)Tercero: como se detalla en la fantástica respuesta de Veedrac ,
tuple.__contains__(x in (y, )es aproximadamente equivalente a(y, ).__contains__(x)) llega al punto de realizar la verificación de identidad más rápido questr.__eq__(nuevamente,x == yes aproximadamente equivalente ax.__eq__(y)).Puede ver evidencia de esto porque
x in (y, )es significativamente más lento que el equivalente lógicox == y:El
x in (y, )caso es más lento porque, después de queisfalla la comparación, elinoperador recurre a la verificación de igualdad normal (es decir, usando==), por lo que la comparación toma aproximadamente la misma cantidad de tiempo que==, haciendo que toda la operación sea más lenta debido a la sobrecarga de crear la tupla , paseando a sus miembros, etc.Tenga en cuenta también que solo
a in (b, )es más rápido cuandoa is b:(¿por qué es
a in (b, )más rápido quea is b or a == b? Supongo que habría menos instrucciones de máquina virtual:a in (b, )son solo ~ 3 instrucciones, dondea is b or a == bhabrá bastantes más instrucciones de VM)La respuesta de Veedrac - https://stackoverflow.com/a/28889838/71522 - entra en mucho más detalle sobre lo que sucede específicamente durante cada uno de
==yinvale la pena leerlo.fuente
X in [X,Y,Z]trabajar correctamente sinX,YoZtener que definir métodos de igualdad (o más bien, la igualdad predeterminada esis, por lo que ahorra tener que invocar__eq__objetos sin definir por el usuario__eq__yisser verdadero debería implicar valor -igualdad).float('nan')es potencialmente engañoso. Es una propiedad denanque no es igual a sí mismo. Eso puede cambiar el tiempo.ina las pruebas de membresía. Cambiaré el nombre de la variable para aclarar.tuple.__contains__se implementa mediantetuplecontainsqué llamadasPyObject_RichCompareBooly que regresa inmediatamente en caso de identidad.unicodetienePyUnicode_RichComparebajo el capó, que tiene el mismo atajo para la identidad."x" is "x"no necesariamente será asíTrue.'x' in ('x', )siempre lo seráTrue, pero puede que no parezca ser más rápido que==.