为什么Python代码在函数中运行得更快?

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

这是为什么呢?


答案 1

在函数内部,字节码为:

  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_CONST               3 (100000000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE        

在顶层,字节码为:

  1           0 SETUP_LOOP              20 (to 23)
              3 LOAD_NAME                0 (xrange)
              6 LOAD_CONST               3 (100000000)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_NAME               1 (i)

  2          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK           
        >>   23 LOAD_CONST               2 (None)
             26 RETURN_VALUE        

不同之处在于,STORE_FASTSTORE_NAME更快(!)。这是因为在函数中,它是局部的,但在完全中它是全局的。i

要检查字节码,请使用 dis 模块。我能够直接反汇编函数,但是要反汇编顶级代码,我必须使用编译内置的代码


答案 2

您可能会问为什么存储局部变量比全局变量更快。这是一个 CPython 实现细节。

请记住,CPython被编译为解释器运行的字节码。编译函数时,局部变量存储在固定大小的数组(不是 )中,变量名称分配给索引。这是可能的,因为您无法向函数动态添加局部变量。然后,从字面上看,检索局部变量是指针查找列表,并且引用计数的增加是微不足道的。dictPyObject

将此与全局查找 () 进行对比,全局查找 () 是涉及哈希等的真实搜索。顺便说一句,这就是为什么您需要指定是否希望它是全局的:如果您曾经在作用域内分配给变量,编译器将发出 s 以获取其访问权限,除非您告诉它不要这样做。LOAD_GLOBALdictglobal iSTORE_FAST

顺便说一句,全局查找仍然非常优化。属性查找是非常缓慢的!foo.bar

这是关于局部变量效率的小插图


推荐