scatter用法 - python直方图横坐标




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

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

请记住,CPython被编译为解释器运行的字节码。 编译函数时,局部变量存储在一个固定大小的数组中( 而不是 dict ),变量名称被分配给索引。 这是可能的,因为你不能动态添加局部变量到一个函数。 然后检索一个局部变量实际上是一个指向查找列表的指针,并且PyObject上的一个refcount增加是微不足道的。

将其与全局查找( LOAD_GLOBAL )进行对比,该查询是包含哈希等的真正dict搜索。 顺便说一句,这就是为什么你需要指定global i如果你希望它是全局的:如果你曾经指定一个范围内的变量,编译器将发出STORE_FAST s的访问,除非你不告诉它。

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

这里是关于局部变量效率的小illustration

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

为什么是这样?


在函数内部,字节码是

  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内置的顶级代码。





cpython