Desde esta página , sabemos que:
Las comparaciones encadenadas son más rápidas que usar el
and
operador. Escribe enx < y < z
lugar dex < y and y < z
.
Sin embargo, obtuve un resultado diferente al probar los siguientes fragmentos de código:
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y < z"
1000000 loops, best of 3: 0.322 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.8" "x < y and y < z"
1000000 loops, best of 3: 0.22 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y < z"
1000000 loops, best of 3: 0.279 usec per loop
$ python -m timeit "x = 1.2" "y = 1.3" "z = 1.1" "x < y and y < z"
1000000 loops, best of 3: 0.215 usec per loop
Parece que x < y and y < z
es más rápido que x < y < z
. ¿Por qué?
Después de buscar algunas publicaciones en este sitio (como este ), sé que "evaluado solo una vez" es la clave x < y < z
, sin embargo, todavía estoy confundido. Para seguir estudiando, desarme estas dos funciones usando dis.dis
:
import dis
def chained_compare():
x = 1.2
y = 1.3
z = 1.1
x < y < z
def and_compare():
x = 1.2
y = 1.3
z = 1.1
x < y and y < z
dis.dis(chained_compare)
dis.dis(and_compare)
Y la salida es:
## chained_compare ##
4 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
5 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
6 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
7 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 DUP_TOP
25 ROT_THREE
26 COMPARE_OP 0 (<)
29 JUMP_IF_FALSE_OR_POP 41
32 LOAD_FAST 2 (z)
35 COMPARE_OP 0 (<)
38 JUMP_FORWARD 2 (to 43)
>> 41 ROT_TWO
42 POP_TOP
>> 43 POP_TOP
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
## and_compare ##
10 0 LOAD_CONST 1 (1.2)
3 STORE_FAST 0 (x)
11 6 LOAD_CONST 2 (1.3)
9 STORE_FAST 1 (y)
12 12 LOAD_CONST 3 (1.1)
15 STORE_FAST 2 (z)
13 18 LOAD_FAST 0 (x)
21 LOAD_FAST 1 (y)
24 COMPARE_OP 0 (<)
27 JUMP_IF_FALSE_OR_POP 39
30 LOAD_FAST 1 (y)
33 LOAD_FAST 2 (z)
36 COMPARE_OP 0 (<)
>> 39 POP_TOP
40 LOAD_CONST 0 (None)
Parece que x < y and y < z
tiene menos comandos disimulados que x < y < z
. ¿Debo considerar x < y and y < z
más rápido que x < y < z
?
Probado con Python 2.7.6 en una CPU Intel (R) Xeon (R) E5640 @ 2.67GHz.
python
performance
zangw
fuente
fuente
timeit
pruebas me interesé en esto.y
no se trata solo de una búsqueda variable, sino de un proceso más costoso como una llamada de función. Es decir,10 < max(range(100)) < 15
es más rápido que10 < max(range(100)) and max(range(100)) < 15
porquemax(range(100))
se llama una vez para ambas comparaciones.Respuestas:
La diferencia es que
x < y < z
y
solo se evalúa una vez. Esto no hace una gran diferencia si y es una variable, pero lo hace cuando se trata de una llamada de función, lo que lleva algún tiempo calcular.fuente
sleep()
función interna?El código de bytes óptimo para las dos funciones que definió sería
porque el resultado de la comparación no se usa. Hagamos que la situación sea más interesante devolviendo el resultado de la comparación. Tengamos también que el resultado no se pueda conocer en tiempo de compilación.
Una vez más, las dos versiones de la comparación son semánticamente idénticas, por lo que el código de bytes óptimo es el mismo para ambas construcciones. Lo mejor que puedo resolver, se vería así. He anotado cada línea con el contenido de la pila antes y después de cada código de operación, en notación Forth (la parte superior de la pila a la derecha,
--
divide antes y después, el seguimiento?
indica algo que podría o no estar allí). Tenga en cuenta queRETURN_VALUE
descarta todo lo que queda en la pila debajo del valor devuelto.Si una implementación del lenguaje, CPython, PyPy, lo que sea, no genera este bytecode (o su propia secuencia equivalente de operaciones) para ambas variaciones, eso demuestra la mala calidad de ese compilador de bytecode . Obtener de las secuencias de código de bytes que publicó en lo anterior es un problema resuelto (creo que todo lo que necesita para este caso es un plegado constante , eliminación de código muerto y un mejor modelado del contenido de la pila; la eliminación de subexpresión común también sería barata y valiosa ), y realmente no hay excusa para no hacerlo en una implementación de lenguaje moderno.
Ahora, sucede que todas las implementaciones actuales del lenguaje tienen compiladores de bytecode de baja calidad. ¡Pero debes ignorar eso mientras codificas! Imagine que el compilador de bytecode es bueno y escriba el código más legible . Probablemente será lo suficientemente rápido de todos modos. Si no es así, busque primero las mejoras algorítmicas y pruebe Cython en segundo lugar; eso proporcionará muchas más mejoras por el mismo esfuerzo que cualquier ajuste de nivel de expresión que pueda aplicar.
fuente
interesting_compare
código de bytes simple en la parte superior (que solo funcionaría con la inserción). Completamente fuera de tema, pero: la integración es una de las optimizaciones más esenciales para cualquier compilador. Puede intentar ejecutar algunos puntos de referencia con say HotSpot en programas reales (no algunas pruebas de matemáticas que pasan el 99% de su tiempo en un bucle dinámico optimizado a mano [y, por lo tanto, no tiene más llamadas a funciones de todos modos]) cuando deshabilita su capacidad de alinear cualquier cosa: verá grandes regresiones.interesting_compare
.y
no cambian la pila, ya que tiene muchas herramientas de depuración.Dado que la diferencia en el resultado parece deberse a la falta de optimización, creo que debería ignorar esa diferencia en la mayoría de los casos; podría ser que la diferencia desaparecerá. La diferencia es porque
y
solo debe evaluarse una vez y eso se resuelve duplicándolo en la pila, lo que requiere un extraPOP_TOP
;LOAD_FAST
sin embargo , la solución para usar podría ser posible.Sin embargo, la diferencia importante es que en
x<y and y<z
el segundoy
debe evaluarse dos veces si sex<y
evalúa como verdadero, esto tiene implicaciones si la evaluacióny
lleva un tiempo considerable o tiene efectos secundarios.En la mayoría de los escenarios, debe usar a
x<y<z
pesar de que es algo más lento.fuente
En primer lugar, su comparación no tiene sentido porque las dos construcciones diferentes no se introdujeron para proporcionar una mejora del rendimiento, por lo que no debe decidir si usar una en lugar de la otra en función de eso.
El
x < y < z
constructo:x
,y
yz
una vez y comprobar si toda la condición se cumple. El usoand
cambia la semántica al evaluary
varias veces, lo que puede cambiar el resultado .Por lo tanto, elija uno en lugar del otro según la semántica que desee y, si son equivalentes, si uno es más legible que el otro.
Dicho esto: un código más desmontado no implica un código más lento. Sin embargo, ejecutar más operaciones de bytecode significa que cada operación es más simple y, sin embargo, requiere una iteración del bucle principal. Esto significa que si las operaciones que está realizando son extremadamente rápidas (por ejemplo, búsqueda de variables locales como lo está haciendo allí), entonces la sobrecarga de ejecutar más operaciones de bytecode puede ser importante.
Pero tenga en cuenta que este resultado no se cumple en la situación más genérica, solo en el "peor de los casos" que le sucede al perfil. Como otros han señalado, si cambias
y
a algo que lleva incluso un poco más de tiempo, verás que los resultados cambian, porque la notación encadenada lo evalúa solo una vez.Resumiendo:
fuente