def main():
for i in xrange(10**8):
pass
main()
Este fragmento de código en Python se ejecuta en (Nota: el tiempo se realiza con la función de tiempo en BASH en Linux).
real 0m1.841s
user 0m1.828s
sys 0m0.012s
Sin embargo, si el bucle for no se coloca dentro de una función,
for i in xrange(10**8):
pass
entonces se ejecuta por mucho más tiempo:
real 0m4.543s
user 0m4.524s
sys 0m0.012s
¿Por qué es esto?
python
performance
profiling
benchmarking
cpython
el jugo
fuente
fuente

Respuestas:
Puede preguntar por qué es más rápido almacenar variables locales que globales. Este es un detalle de implementación de CPython.
Recuerde que CPython se compila en bytecode, que ejecuta el intérprete. Cuando se compila una función, las variables locales se almacenan en una matriz de tamaño fijo ( no a
dict) y los nombres de las variables se asignan a los índices. Esto es posible porque no puede agregar dinámicamente variables locales a una función. Luego, recuperar una variable local es literalmente una búsqueda de puntero en la lista y un aumento de recuento en elPyObjectque es trivial.Contraste esto con una búsqueda global (
LOAD_GLOBAL), que es unadictbúsqueda verdadera que implica un hash, etc. Por cierto, es por eso que debe especificarglobal isi desea que sea global: si alguna vez asigna a una variable dentro de un ámbito, el compilador emitiráSTORE_FASTs para su acceso a menos que se lo indique.Por cierto, las búsquedas globales todavía están bastante optimizadas. ¡Las búsquedas de atributos
foo.barson realmente lentas!Aquí hay una pequeña ilustración sobre la eficiencia variable local.
fuente
def foo_func: x = 5,xes local para una función. El accesoxes local.foo = SomeClass(),foo.bares el acceso al atributo.val = 5global es global. En cuanto a la velocidad local> global> atributo de acuerdo con lo que he leído aquí. Por lo tanto el accesoxenfoo_funces el más rápido, seguido porval, seguido defoo.bar.foo.attrno es una búsqueda local porque en el contexto de esta convo, estamos hablando de búsquedas locales como una búsqueda de una variable que pertenece a una función.globals()función. Si desea más información que esa, es posible que deba comenzar a buscar el código fuente de Python. Y CPython es solo el nombre de la implementación habitual de Python, por lo que probablemente ya lo esté utilizando.Dentro de una función, el código de bytes es:
En el nivel superior, el código de bytes es:
La diferencia es que
STORE_FASTes más rápido (!) QueSTORE_NAME. Esto se debe a que en una función,ies un local, pero a nivel global es un global.Para examinar el código de bytes, use el
dismódulo . Pude desmontar la función directamente, pero para desmontar el código de nivel superior tuve que usar elcompileincorporado .fuente
global ien lamainfunción hace que los tiempos de ejecución sean equivalentes.locals(), vía ,inspect.getframe()etc.). Buscar un elemento de matriz por un entero constante es mucho más rápido que buscar un dict.Además de los tiempos de almacenamiento de variables locales / globales, la predicción de código de operación hace que la función sea más rápida.
Como explican las otras respuestas, la función usa el
STORE_FASTcódigo de operación en el bucle. Aquí está el código de bytes para el bucle de la función:Normalmente, cuando se ejecuta un programa, Python ejecuta cada código de operación uno tras otro, haciendo un seguimiento de la pila y realizando otras comprobaciones en el marco de la pila después de ejecutar cada código de operación. La predicción del código de operación significa que, en ciertos casos, Python puede saltar directamente al siguiente código de operación, evitando así parte de esta sobrecarga.
En este caso, cada vez que Python vea
FOR_ITER(la parte superior del bucle), "predecirá"STORE_FASTcuál es el próximo código de operación que debe ejecutar. Python luego mira el siguiente código de operación y, si la predicción fue correcta, salta directamente aSTORE_FAST. Esto tiene el efecto de comprimir los dos códigos de operación en un solo código de operación.Por otro lado, el
STORE_NAMEcódigo de operación se usa en el ciclo a nivel global. Python * no * hace predicciones similares cuando ve este código de operación. En cambio, debe volver a la parte superior del ciclo de evaluación, lo que tiene implicaciones obvias para la velocidad a la que se ejecuta el ciclo.Para dar más detalles técnicos sobre esta optimización, aquí hay una cita del
ceval.carchivo (el "motor" de la máquina virtual de Python):Podemos ver en el código fuente del código de
FOR_ITERoperación exactamente dónde se realiza la predicciónSTORE_FAST:La
PREDICTfunción se expande a,if (*next_instr == op) goto PRED_##opes decir, simplemente saltamos al inicio del código de operación predicho. En este caso, saltamos aquí:La variable local ahora está configurada y el siguiente código operativo está en ejecución. Python continúa a través del iterable hasta que llega al final, haciendo la predicción exitosa cada vez.
La página wiki de Python tiene más información sobre cómo funciona la máquina virtual de CPython.
fuente
HAS_ARGprueba nunca ocurre (excepto cuando el rastreo de bajo nivel está habilitado tanto en la compilación como en el tiempo de ejecución, lo que no ocurre en la construcción normal), dejando solo un salto impredecible.PREDICTmacro está completamente deshabilitada; en su lugar, la mayoría de los casos terminan en unaDISPATCHrama directa. Pero en las CPU de predicción de bifurcación, el efecto es similar al de laPREDICTbifurcación (y la predicción) es por código de operación, lo que aumenta las probabilidades de una predicción de bifurcación exitosa.