【发布时间】:2012-09-17 09:21:44
【问题描述】:
我还是 Python 的新手,我一直在努力提高我的 Python 脚本的性能,所以我在使用和不使用全局变量的情况下对其进行了测试。我对它进行了计时,令我惊讶的是,它在声明全局变量而不是将本地变量传递给函数时运行得更快。这是怎么回事?我认为局部变量的执行速度更快? (我知道全局变量不安全,我还是很好奇。)
【问题讨论】:
标签: python performance
我还是 Python 的新手,我一直在努力提高我的 Python 脚本的性能,所以我在使用和不使用全局变量的情况下对其进行了测试。我对它进行了计时,令我惊讶的是,它在声明全局变量而不是将本地变量传递给函数时运行得更快。这是怎么回事?我认为局部变量的执行速度更快? (我知道全局变量不安全,我还是很好奇。)
【问题讨论】:
标签: python performance
根据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。这将告诉您什么花费的时间最多,因此您可以专注于此。你的流程应该是这样的:
【讨论】:
简单回答:
由于 Python 的动态特性,当解释器遇到像 abc 这样的表达式时,它会查找 a(首先尝试本地命名空间,然后是全局命名空间,最后是内置命名空间),然后查找该对象的名称空间来解析名称 b,最后它会在该对象的名称空间中查找名称以解析名称 c。这些查找相当快;对于局部变量,查找速度非常快,因为解释器知道哪些变量是局部变量,并且可以为它们分配内存中的已知位置。
解释器知道函数中的哪些名称是本地名称,并在函数调用的内存中为它们分配特定(已知)的位置。这使得对局部变量的引用比对全局变量和(尤其是)对内置函数的引用要快得多。
解释相同的代码示例:
>>> 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
>>>
【讨论】:
当 Python 编译一个函数时,该函数在调用它之前就知道其中的变量是局部变量、闭包还是全局变量。
我们有几种方法在函数中引用变量:
让我们在几个不同的函数中创建这些类型的变量,以便我们自己查看:
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
我们可以看到每个函数都知道在哪里查找变量——它不需要在运行时这样做:
>>> 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
我会添加其他系统,因为我有机会测试它们。
【讨论】:
您未包括的时间是程序员在跟踪使用全局时创建的错误所花费的时间,这些错误会在您的程序的其他地方产生副作用。这个时间比创建和释放局部变量所花费的时间要多很多倍,
【讨论】: