def main():
for i in xrange(10**8):
pass
main()
Python中的这段代码运行(注意:时间是在Linux中的BASH中使用时间函数完成的 . )
real 0m1.841s
user 0m1.828s
sys 0m0.012s
但是,如果for循环没有放在函数中,
for i in xrange(10**8):
pass
然后它会运行更长的时间:
real 0m4.543s
user 0m4.524s
sys 0m0.012s
为什么是这样?
3 回答
您可能会问为什么存储局部变量比全局变量更快 . 这是一个CPython实现细节 .
请记住,CPython被编译为字节码,解释器运行 . 编译函数时,局部变量存储在固定大小的数组(不是
dict
)中,并且变量名称将分配给索引 . 这是可能的,因为您无法动态地将局部变量添加到函数中 . 然后检索局部变量实际上是指向列表的指针查找和PyObject
上的refcount增加,这是微不足道的 .将此与全局查找(
LOAD_GLOBAL
)进行对比,这是一个涉及哈希等的真正的dict
搜索 . 顺便说一句,这就是为什么你需要指定global i
,如果你想要它是全局的:如果你曾经分配给一个范围内的变量,编译器将发出STORE_FAST
s进行访问,除非你告诉它不要 .顺便说一下,全局查找仍然相当优化 . 属性查找
foo.bar
真的很慢!这是关于局部变量效率的小illustration .
除了本地/全局变量存储时间之外, opcode prediction 使功能更快 .
正如其他答案所解释的那样,该函数在循环中使用
STORE_FAST
操作码 . 这里's the bytecode for the function'循环:通常,当程序运行时,Python会一个接一个地执行每个操作码,跟踪堆栈并在执行每个操作码后对堆栈帧执行其他检查 . 操作码预测意味着在某些情况下Python能够直接跳转到下一个操作码,从而避免了一些开销 .
在这种情况下,每当Python看到
FOR_ITER
(循环的顶部)时,它将"predict"STORE_FAST
是它必须执行的下一个操作码 . 然后Python会查看下一个操作码,如果预测正确,它会直接跳到STORE_FAST
. 这具有将两个操作码压缩成单个操作码的效果 .另一方面,
STORE_NAME
操作码在全局级别的循环中使用 . 当它看到这个操作码时,Python会做 not 做类似的预测 . 相反,它必须回到评估循环的顶部,这对循环执行的速度有明显的影响 .要提供有关此优化的更多技术细节,请参阅ceval.c文件(Python的虚拟机的"engine"):
我们可以在源代码中看到FOR_ITER操作码的确切位置是
STORE_FAST
的预测:PREDICT
函数扩展为if (*next_instr == op) goto PRED_##op
,即我们只是跳转到预测操作码的开头 . 在这种情况下,我们跳到这里:现在设置了局部变量,并且下一个操作码已启动执行 . Python继续贯穿迭代,直到它到达终点,每次都成功进行预测 .
Python wiki page提供了有关CPython虚拟机如何工作的更多信息 .
在函数内部,字节码是
在顶层,字节码是
区别在于STORE_FAST比STORE_NAME更快(!) . 这是因为在函数中,
i
是本地的,但在顶层它是全局的 .要检查字节码,请使用dis module . 我能够直接反汇编函数,但是要反汇编顶层代码我必须使用compile builtin .