为什么代码在一个函数里运行的更快

比如同样的一段代码,放在函数里:

def main():
    for i in xrange(10**8):
        pass
main()

运行时间:

real    0m1.841s
user    0m1.828s
sys     0m0.012s

不放在函数里:

for i in xrange(10**8):
    pass

运行时间:

real    0m4.543s
user    0m4.524s
sys     0m0.012s

解答

这是因为存取一个本地变量比全局变量要快,这有关 CPython 的实现细节。

记住 CPython 解析器运行的是被编译过的字节编码(bytecode)。当一个函数被编译后,局部变量被存储在了固定大小的数组(不是一个 dict),而变量名赋值给了索引。于是你不能动态地为一个函数添加局部变量,检查一个局部变量就好像是一个指针去查找列表,对于在 PyObject 上的引用计数增长是微不足道的

在查找全局变量(LOAD_GLOBAL)时,涉及到一个实实在在的 dict 的哈希查找。同时,这也是你想要一个全局变量时需要加上 global i 的原因:如果你在一个区域内指定变量,编译器就会建立一个 STORE_FAST 的入口,除非你不让它这么做。

全局查找速度其实也不慢,真正拖慢速度的是像 foo.bar 这样的属性查找

对比函数和全局的字节码:

# 函数
  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 模块可以看字节编码,可以直接解析函数。但是要解析全局代码必须用 compile 内建模块

Last updated