jan*_*neh 25 python performance
我还是Python新手,我一直在努力提高Python脚本的性能,所以我测试了它是否有全局变量.我把它计时了,令我惊讶的是,它在声明全局变量时运行得更快,而不是将局部变量传递给函数.这是怎么回事?我认为局部变量的执行速度更快?(我知道全局变量不安全,我仍然很好奇.)
Rei*_*ica 31
根据本地和全局的这个页面:
当一行代码询问变量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()
Run Code Online (Sandbox Code Playgroud)
而全球版本大约需要0.7(Python 3中为0.5):
def func():
global x
for i in range(1000000):
x = 5
func()
Run Code Online (Sandbox Code Playgroud)
global 对已经全局化的变量做了一些奇怪的事情有趣的是,这个版本在0.8秒内运行:
global x
x = 5
for i in range(10000000):
x = 5
Run Code Online (Sandbox Code Playgroud)
虽然这在0.9中运行:
x = 5
for i in range(10000000):
x = 5
Run Code Online (Sandbox Code Playgroud)
您会注意到,在这两种情况下,x都是一个全局变量(因为没有函数),并且它们都比使用本地变慢.我不知道为什么global x在这种情况下声明帮助.
Python 3中不会出现这种奇怪现象(两个版本大约需要0.6秒).
如果您想优化您的程序,您可以做的最好的事情就是对其进行分析.这将告诉您花费最多的时间,因此您可以专注于此.你的过程应该是这样的:
Moh*_*sin 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
>>>
Run Code Online (Sandbox Code Playgroud)
当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
Run Code Online (Sandbox Code Playgroud)
我们可以看到每个函数都知道在哪里查找变量 - 它不需要在运行时这样做:
>>> 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
Run Code Online (Sandbox Code Playgroud)
我们可以看到,当前全局的字节码是LOAD_GLOBAL,闭包变量是LOAD_DEREF,而本地是LOAD_FAST.这些是CPython的实现细节,可能会随版本而变化 - 但是能够看到Python以不同方式处理每个变量查找是很有用的.
粘贴到翻译中并亲自查看:
import dis
dis.dis(globalfoo)
dis.dis(closurefoo)
dis.dis(defaultfoo)
dis.dis(localfoo)
Run Code Online (Sandbox Code Playgroud)
测试代码(随意在您的系统上测试):
import sys
sys.version
import timeit
min(timeit.repeat(globalfoo))
min(timeit.repeat(closurefoo))
min(timeit.repeat(defaultfoo))
min(timeit.repeat(localfoo))
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
在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
Run Code Online (Sandbox Code Playgroud)
我将添加其他系统,因为我有机会测试它们.