Esta es una pregunta de seguimiento a una respuesta que di hace unos días . Editar: parece que el OP de esa pregunta ya usaba el código que le publiqué para hacerle la misma pregunta , pero no estaba al tanto. Disculpas Sin embargo, las respuestas proporcionadas son diferentes.
Sustancialmente observé que:
>>> def without_else(param=False):
... if param:
... return 1
... return 0
>>> def with_else(param=False):
... if param:
... return 1
... else:
... return 0
>>> from timeit import Timer as T
>>> T(lambda : without_else()).repeat()
[0.3011460304260254, 0.2866089344024658, 0.2871549129486084]
>>> T(lambda : with_else()).repeat()
[0.27536892890930176, 0.2693932056427002, 0.27011704444885254]
>>> T(lambda : without_else(True)).repeat()
[0.3383951187133789, 0.32756996154785156, 0.3279120922088623]
>>> T(lambda : with_else(True)).repeat()
[0.3305950164794922, 0.32186388969421387, 0.3209099769592285]
... o en otras palabras: tener la else
cláusula es más rápida independientemente de la if
condición que se active o no.
Supongo que tiene que ver con un código de bytes diferente generado por los dos, pero ¿alguien puede confirmar / explicar en detalle?
EDITAR: Parece que no todos pueden reproducir mis tiempos, por lo que pensé que podría ser útil dar información sobre mi sistema. Estoy ejecutando Ubuntu 11.10 64 bit con el python predeterminado instalado. python
genera la siguiente información de versión:
Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Estos son los resultados del desmontaje en Python 2.7:
>>> dis.dis(without_else)
2 0 LOAD_FAST 0 (param)
3 POP_JUMP_IF_FALSE 10
3 6 LOAD_CONST 1 (1)
9 RETURN_VALUE
4 >> 10 LOAD_CONST 2 (0)
13 RETURN_VALUE
>>> dis.dis(with_else)
2 0 LOAD_FAST 0 (param)
3 POP_JUMP_IF_FALSE 10
3 6 LOAD_CONST 1 (1)
9 RETURN_VALUE
5 >> 10 LOAD_CONST 2 (0)
13 RETURN_VALUE
14 LOAD_CONST 0 (None)
17 RETURN_VALUE
LOAD_CONST(None); RETURN_VALUE
pero, como se dijo, nunca se alcanza) al final dewith_else
. Dudo mucho que el código muerto haga que una función sea más rápida. ¿Alguien podría proporcionar undis
2.7?else
yFalse
fue el más lento de todos (152ns). El segundo más rápido fueTrue
sinelse
(143ns) y otros dos fueron básicamente iguales (137ns y 138ns). No%timeit
utilicé el parámetro predeterminado y lo midí en iPython.with_else
es notablemente más rápido.Respuestas:
Es una suposición pura, y no he descubierto una manera fácil de verificar si es correcta, pero tengo una teoría para ti.
Probé su código y obtuve el mismo resultado,
without_else()
es repetidamente un poco más lento quewith_else()
:Teniendo en cuenta que el código de bytes es idéntico, la única diferencia es el nombre de la función. En particular, la prueba de tiempo busca el nombre global. Intente cambiar
without_else()
el nombre y la diferencia desaparecerá:Supongo que
without_else
tiene una colisión hash con algo más, porglobals()
lo que la búsqueda de nombres globales es un poco más lenta.Editar : un diccionario con 7 u 8 teclas probablemente tiene 32 ranuras, por lo que, sobre esa base,
without_else
tiene una colisión hash con__builtins__
:Para aclarar cómo funciona el hash:
__builtins__
hashes a -1196389688 que redujo el módulo del tamaño de la tabla (32) significa que se almacena en la ranura # 8 de la tabla.without_else
hashes a 505688136 que redujo el módulo 32 es 8, por lo que hay una colisión. Para resolver este cálculo de Python:Empezando con:
Repita esto hasta que encontremos un espacio libre:
lo que le da 17 para usar como el próximo índice. Afortunadamente, es gratis, por lo que el ciclo solo se repite una vez. El tamaño de la tabla hash es una potencia de 2, por lo que
2**i
es el tamaño de la tabla hash,i
es el número de bits utilizados a partir del valor hashj
.Cada sonda en la tabla puede encontrar uno de estos:
La ranura está vacía, en ese caso la prueba se detiene y sabemos que el valor no está en la tabla.
La ranura no se usó, pero se usó en el pasado, en cuyo caso intentaremos el siguiente valor calculado como se indicó anteriormente.
El espacio está lleno, pero el valor hash completo almacenado en la tabla no es el mismo que el hash de la clave que estamos buscando (eso es lo que sucede en el caso de
__builtins__
vswithout_else
).El espacio está lleno y tiene exactamente el valor hash que queremos, luego Python verifica si la clave y el objeto que estamos buscando son el mismo objeto (que en este caso serán porque las cadenas cortas que podrían ser identificadores están internados, por lo que los identificadores idénticos usan exactamente la misma cadena).
Finalmente, cuando el espacio está lleno, el hash coincide exactamente, pero las claves no son el objeto idéntico, entonces y solo entonces Python intentará compararlas para la igualdad. Esto es relativamente lento, pero en el caso de las búsquedas de nombres en realidad no debería suceder.
fuente