2011-11-19 13 views
7

Esto es bastante malo para la micro-optimización, pero solo tengo curiosidad. Por lo general, no hace una diferencia en el mundo "real".¿Por qué una llamada de función vacía en Python es un 15% más lenta para el código python compilado dinámicamente?

Así que estoy compilando una función (que no hace nada) usando compile() y luego llamando al exec en ese código y obteniendo una referencia a la función que compilé. Entonces lo ejecuto un par de millones de veces y lo sincronizo. Luego repitiéndolo con una función local. ¿Por qué la función compilada dinámicamente es un 15% más lenta (en Python 2.7.2) solo para la llamada?

import datetime 
def getCompiledFunc(): 
    cc = compile("def aa():pass", '<string>', 'exec') 
    dd = {} 
    exec cc in dd 
    return dd.get('aa') 

compiledFunc = getCompiledFunc() 
def localFunc():pass 


def testCall(f): 
    st = datetime.datetime.now() 
    for x in xrange(10000000): f() 
    et = datetime.datetime.now() 
    return (et-st).total_seconds() 

for x in xrange(10): 
    lt = testCall(localFunc) 
    ct = testCall(compiledFunc) 
    print "%s %s %s%% slower" % (lt, ct, int(100.0*(ct-lt)/lt)) 

La salida que estoy recibiendo es algo así como:

1.139 1.319 15% slower 
+0

cambio de contexto? Es decir. cuando llama a la función compilada, ¿todavía entra en un nuevo ámbito, ejecuta la función y devuelve el resultado del alcance? (No soy un experto en pitón, solo una suposición) –

+6

Sus medidas están desactivadas. Use 'timeit' para obtener mediciones imparciales, y los resultados serán idénticos (lo intenté, lo son). Los dos objetos de función son indistinguibles, y tienen un código de bytes idéntico. –

+1

@SvenMarnach Respuesta con contador microbenchmark? –

Respuesta

11

La función dis.dis() muestra que el objeto de código para cada versión es idéntica:

aa 
    1   0 LOAD_CONST    0 (None) 
       3 RETURN_VALUE   
localFunc 
10   0 LOAD_CONST    0 (None) 
       3 RETURN_VALUE 

Así que la diferencia está en el objeto de función. Comparé cada uno de los campos (func_doc, func_closure, etc.) y el que es diferente es func_globals. En otras palabras, localFunc.func_globals != compiledFunc.func_globals.

Hay un costo por el suministro de su propio diccionario en lugar de los globales incorporados (el primero debe buscarse cuando se crea un marco de pila en cada llamada y este último puede ser referenciado directamente por el código C que ya sabe sobre el diccionario global predeterminado incorporado).

Esto se comprueba fácilmente cambiando la línea deejecutivo en el código para:

exec cc in globals(), dd 

Con que cambio, la diferencia de tiempo desaparece.

Misterio resuelto!

Cuestiones relacionadas