rank vote url
44 455 162 534 url

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

  1. def main():
  2. for i in xrange(10**8):
  3. pass
  4. main()

在Python中运行速度:

  1. real 0m1.841s
  2. user 0m1.828s
  3. sys 0m0.012s

然而不把它放在函数里:

  1. for i in xrange(10**8):
  2. pass

它比上一个运行时间更长

  1. real 0m4.543s
  2. user 0m4.524s
  3. sys 0m0.012s

为啥会这样?

注:这个计时是用LinuxBASH中的时间函数.


你或许想问为什么存取一个本地变量比全局变量要快.这有关于CPython的实现细节.

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

相反的在查找全局变量(LOAD_GLOBAL)时,涉及到的是一个实实在在的dict的哈希查找.顺便说一句,这就是为什么当你想要一个全局变量你必须要在前面加上global i:如果你在一个区域内指定一个变量,编译器就会建立一个STORE_FAST的入口,除非你不让它那么做.

在说一句,全局查找速度也不慢.真正拖慢速度的是像foo.bar这样的属性查找!


在一个函数里,字节码是这样的:

  1. 2 0 SETUP_LOOP 20 (to 23)
  2. 3 LOAD_GLOBAL 0 (xrange)
  3. 6 LOAD_CONST 3 (100000000)
  4. 9 CALL_FUNCTION 1
  5. 12 GET_ITER
  6. >> 13 FOR_ITER 6 (to 22)
  7. 16 STORE_FAST 0 (i)
  8. 3 19 JUMP_ABSOLUTE 13
  9. >> 22 POP_BLOCK
  10. >> 23 LOAD_CONST 0 (None)
  11. 26 RETURN_VALUE

如果在全局,字节码:

  1. 1 0 SETUP_LOOP 20 (to 23)
  2. 3 LOAD_NAME 0 (xrange)
  3. 6 LOAD_CONST 3 (100000000)
  4. 9 CALL_FUNCTION 1
  5. 12 GET_ITER
  6. >> 13 FOR_ITER 6 (to 22)
  7. 16 STORE_NAME 1 (i)
  8. 2 19 JUMP_ABSOLUTE 13
  9. >> 22 POP_BLOCK
  10. >> 23 LOAD_CONST 2 (None)
  11. 26 RETURN_VALUE

两者的区别是STORE_FASTSTORE_NAME要快很多.这是因为在函数里i是一个局部变量而在全局区域它是一个全局变量.

可以看一下字节编码,用到了dis模块.我可以直接解析函数,但是要解析全局代码必须用compile内建模块.