Python - 全局变量与本地变量的性能

时间:2012-09-25 19:49:48

标签: python performance

我还是Python的新手,我一直在努力提高Python脚本的性能,因此我使用和不使用全局变量对其进行了测试。我把它计时了,令我惊讶的是,它在声明全局变量时运行得更快,而不是将局部变量传递给函数。这是怎么回事?我认为局部变量的执行速度更快? (我知道全局变量不安全,我仍然很好奇。)

4 个答案:

答案 0 :(得分:30)

当地人应该更快

根据this page on locals and globals

  

当一行代码询问变量x的值时,Python将按顺序在所有可用名称空间中搜索该变量:

     
      
  • 本地命名空间 - 特定于当前函数或类方法。如果函数定义了局部变量x,或者有一个参数x,Python将使用它并停止搜索。
  •   
  • 全局命名空间 - 特定于当前模块。如果模块定义了一个名为x的变量,函数或类,Python将使用它并停止搜索。
  •   
  • 内置命名空间 - 所有模块的全局。作为最后的手段,Python将假设x是内置函数或变量的名称。
  •   

基于此,我假设局部变量通常更快。我的猜测是你所看到的是你的剧本特别的东西。

当地人更快

这是一个使用局部变量的简单示例,在我的机器上花费约0.5秒(Python 3中为0.3):

def func():
    for i in range(10000000):
        x = 5

func()

全球版本,大约需要0.7(Python 3中为0.5):

def func():
    global x
    for i in range(1000000):
        x = 5

func()

global对已经全局

的变量做了一些奇怪的事情

有趣的是,这个版本在0.8秒内运行:

global x
x = 5
for i in range(10000000):
    x = 5

虽然这是在0.9:

x = 5
for i in range(10000000):
    x = 5

你会注意到,在这两种情况下,x都是一个全局变量(因为没有函数),并且它们都比使用本地变慢。我不知道为什么在这种情况下声明global x有帮助。

Python 3中不会出现这种奇怪现象(两个版本大约需要0.6秒)。

更好的优化方法

如果您想优化您的计划,您可以做的最好的事情是profile it。这将告诉您花费最多的时间,因此您可以专注于此。你的过程应该是这样的:

  1. 运行您的程序并进行性能分析。
  2. 查看KCacheGrind或类似程序中的配置文件,以确定哪些功能占用的时间最多。
  3. 在这些功能中:
    • 查找可以缓存函数结果的地方(这样您就不必做太多工作)。
    • 寻找算法改进,例如用封闭形式的函数替换递归函数,或用字典替换列表搜索。
    • 重新调整以确保该功能仍然存在问题。
    • 考虑使用multiprocessing

答案 1 :(得分:10)

您不包括的时间是程序员花时间跟踪在使用全局时创建的错误在程序中的其他位置产生的副作用。那个时间比创建和释放局部变量所花费的时间多很多倍,

答案 2 :(得分:10)

简单回答:

由于Python的动态特性,当解释器遇到类似abc的表达式时,它会查找(首先尝试本地命名空间,然后是全局命名空间,最后是内置命名空间),然后查找该对象的用于解析名称b的命名空间,最后它查找该对象的命名空间以解析名称c。这些查找速度相当快;对于局部变量,查找非常快,因为解释器知道哪些变量是本地的,并且可以在内存中为它们分配已知位置。

解释器知道函数中的哪些名称是本地的,并且它们在函数调用的内存中分配特定的(已知的)位置。这使得对locals的引用比对于globals和(尤其是)内置函数更快。

解释相同的代码示例:

>>> glen = len # provides a global reference to a built-in
>>> 
>>> def flocal():
...     name = len
...     for i in range(25):
...         x = name
... 
>>> def fglobal():
...     for i in range(25):
...         x = glen
... 
>>> def fbuiltin():
...     for i in range(25): 
...         x = len
... 
>>> timeit("flocal()", "from __main__ import flocal")
1.743438959121704
>>> timeit("fglobal()", "from __main__ import fglobal")
2.192162036895752
>>> timeit("fbuiltin()", "from __main__ import fbuiltin")
2.259413003921509
>>> 

答案 3 :(得分:5)

当Python编译一个函数时,如果函数中的变量是locals,closures或globals,则函数在调用之前就知道了。

我们有几种在函数中引用变量的方法:

  • 全局
  • 当地人

因此,让我们在几个不同的函数中创建这些变量,以便我们自己看看:

global_foo = 'foo'
def globalfoo():
    return global_foo

def makeclosurefoo():
    boundfoo = 'foo'
    def innerfoo():
        return boundfoo
    return innerfoo

closurefoo = makeclosurefoo()

def defaultfoo(foo='foo'):
    return foo

def localfoo():
    foo = 'foo'
    return foo

Dissassembled

我们可以看到每个函数都知道在哪里查找变量 - 它不需要在运行时这样做:

>>> import dis
>>> dis.dis(globalfoo)
  2           0 LOAD_GLOBAL              0 (global_foo)
              2 RETURN_VALUE
>>> dis.dis(closurefoo)
  4           0 LOAD_DEREF               0 (boundfoo)
              2 RETURN_VALUE
>>> dis.dis(defaultfoo)
  2           0 LOAD_FAST                0 (foo)
              2 RETURN_VALUE
>>> dis.dis(localfoo)
  2           0 LOAD_CONST               1 ('foo')
              2 STORE_FAST               0 (foo)

  3           4 LOAD_FAST                0 (foo)
              6 RETURN_VALUE

我们可以看到,当前全局的字节码是LOAD_GLOBAL,闭包变量是LOAD_DEREF,本地是LOAD_FAST。这些是CPython的实现细节,可能会在不同版本之间发生变化 - 但是能够看到Python以不同方式处理每个变量查找是很有用的。

粘贴到翻译中并自己查看:

import dis
dis.dis(globalfoo)
dis.dis(closurefoo)
dis.dis(defaultfoo)
dis.dis(localfoo)

测试代码

测试代码(随时在您的系统上测试):

import sys
sys.version
import timeit
min(timeit.repeat(globalfoo))
min(timeit.repeat(closurefoo))
min(timeit.repeat(defaultfoo))
min(timeit.repeat(localfoo))

输出

在Windows上,至少在这个版本中,看起来闭包会受到一点点惩罚 - 使用本地默认是最快的,因为你不必分配本地每一次:

>>> import sys
>>> sys.version
'3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
>>> import timeit
>>> min(timeit.repeat(globalfoo))
0.0728403456180331
>>> min(timeit.repeat(closurefoo))
0.07465484920749077
>>> min(timeit.repeat(defaultfoo))
0.06542038103088998
>>> min(timeit.repeat(localfoo))
0.06801849537714588

在Linux上:

>>> import sys
>>> sys.version
'3.6.4 |Anaconda custom (64-bit)| (default, Mar 13 2018, 01:15:57) \n[GCC 7.2.0]'
>>> import timeit
>>> min(timeit.repeat(globalfoo))
0.08560040907468647
>>> min(timeit.repeat(closurefoo))
0.08592104795388877
>>> min(timeit.repeat(defaultfoo))
0.06587386003229767
>>> min(timeit.repeat(localfoo))
0.06887826602905989

我会添加其他系统,因为我有机会测试它们。