为什么在动态编译的Python代码中,空函数调用速度慢15%?
这其实是个很小的优化问题,但我只是好奇一下。在“真实”世界中,这通常没有太大区别。
我在用 compile()
编译一个什么都不做的函数,然后用 exec
来执行这段代码,并获取我编译的函数的引用。接着,我执行这个函数几百万次并测量时间。然后再用一个本地函数重复这个过程。为什么在 Python 2.7.2 中,动态编译的函数调用速度大约慢了 15% 呢?
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))
我得到的输出大概是这样的:
1.139 1.319 15% slower
1 个回答
11
dis.dis() 这个函数可以显示每个版本的代码对象是一样的:
aa
1 0 LOAD_CONST 0 (None)
3 RETURN_VALUE
localFunc
10 0 LOAD_CONST 0 (None)
3 RETURN_VALUE
所以,区别在于函数对象。我对每个字段(比如 func_doc、func_closure 等)进行了比较,发现唯一不同的是 func_globals。换句话说,localFunc.func_globals != compiledFunc.func_globals
。
使用你自己的字典而不是内置的全局字典是有代价的(前者在每次调用时创建堆栈帧时需要查找,而后者可以直接被已经知道默认内置全局字典的 C 代码引用)。
这很容易通过将你的代码中的 exec 行改为以下内容来验证:
exec cc in globals(), dd
通过这个改变,时间差就消失了。
谜团解开了!