>>> 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 in
todas partes en lugar de ==
!
python
performance
python-3.x
python-internals
Markus Meskanen
fuente
fuente
in
todas partes en lugar de==
. Es una optimización prematura que perjudica la legibilidad.x ="!foo"
x in ("!foo",)
yx == "!foo"
in
lugar 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_OP
ya que ese es el bytecode involucradoEsto muestra los valores de la pila (técnicamente solo muestra uno)
y ejecuta la comparación:
cmp_outcome
Es esto:Aquí es donde se dividen los caminos. La
PyCmp_IN
rama 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_RichCompareBool
lo 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_RichCompareBool
casi termina inmediatamente. ParaPyObject_RichCompare
, lo haceEl
Py_EnterRecursiveCall
/Py_LeaveRecursiveCall
combo 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_richcompare
hace:Esto hace algunas verificaciones rápidas para llamar,
v->ob_type->tp_richcompare
que 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_Check
yPyUnicode_READY
son 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í.in
solo hace menos.fuente
Aquí hay tres factores en juego que, combinados, producen este comportamiento sorprendente.
Primero: el
in
operador 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!
is
debe 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 == y
es 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 queis
falla la comparación, elin
operador 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 == b
habrá 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
==
yin
vale la pena leerlo.fuente
X in [X,Y,Z]
trabajar correctamente sinX
,Y
oZ
tener 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__
yis
ser verdadero debería implicar valor -igualdad).float('nan')
es potencialmente engañoso. Es una propiedad denan
que no es igual a sí mismo. Eso puede cambiar el tiempo.in
a las pruebas de membresía. Cambiaré el nombre de la variable para aclarar.tuple.__contains__
se implementa mediantetuplecontains
qué llamadasPyObject_RichCompareBool
y que regresa inmediatamente en caso de identidad.unicode
tienePyUnicode_RichCompare
bajo 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==
.