简体   繁体   English

全局变量与本地的性能

[英]Performance with global variables vs local

I am still new to Python, and I have been trying to improve the performance of my Python script, so I tested it with and without global variables.我对 Python 还是个新手,我一直在努力提高我的 Python 脚本的性能,所以我在使用和不使用全局变量的情况下对其进行了测试。 I timed it, and to my surprise, it ran faster with global variables declared rather than passing local vars to functions.我给它计时,令我惊讶的是,它在声明全局变量的情况下运行得更快,而不是将局部变量传递给函数。 What's going on?这是怎么回事? I thought execution speed was faster with local variables?我认为局部变量的执行速度更快? (I know globals are not safe, I am still curious.) (我知道全局变量不安全,我仍然很好奇。)

Locals should be faster当地人应该更快

According to this page on locals and globals :根据本地人和全球人的这个页面

When a line of code asks for the value of a variable x, Python will search for that variable in all the available namespaces, in order:当一行代码询问变量 x 的值时,Python 将在所有可用的命名空间中搜索该变量,顺序为:

  • local namespace - specific to the current function or class method.本地命名空间- 特定于当前函数或类方法。 If the function defines a local variable x, or has an argument x, Python will use this and stop searching.如果函数定义了一个局部变量 x,或者有一个参数 x,Python 将使用它并停止搜索。
  • global namespace - specific to the current module.全局命名空间- 特定于当前模块。 If the module has defined a variable, function, or class called x, Python will use that and stop searching.如果模块定义了一个名为 x 的变量、函数或类,Python 将使用它并停止搜索。
  • built-in namespace - global to all modules.内置命名空间- 所有模块都是全局的。 As a last resort, Python will assume that x is the name of built-in function or variable.作为最后的手段,Python 将假定 x 是内置函数或变量的名称。

Based on that, I'd assume that local variables are generally faster.基于此,我假设局部变量通常更快。 My guess is what you're seeing is something particular about your script.我的猜测是您所看到的内容与您的脚本有关。

Locals are faster当地人更快

Here's a trivial example using a local variable, which takes about 0.5 seconds on my machine (0.3 in Python 3):这是一个使用局部变量的简单示例,在我的机器上大约需要 0.5 秒(在 Python 3 中为 0.3):

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

func()

And the global version, which takes about 0.7 (0.5 in Python 3):而全局版本,大约需要 0.7(在 Python 3 中为 0.5):

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

func()

global does something weird to variables that are already global global对已经是全局的变量做了一些奇怪的事情

Interestingly, this version runs in 0.8 seconds:有趣的是,这个版本在 0.8 秒内运行:

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

While this runs in 0.9:虽然这在 0.9 中运行:

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

You'll notice that in both cases, x is a global variable (since there's no functions), and they're both slower than using locals.您会注意到,在这两种情况下, x都是一个全局变量(因为没有函数),而且它们都比使用本地变量慢。 I have no clue why declaring global x helped in this case.我不知道为什么在这种情况下声明global x帮助。

This weirdness doesn't occur in Python 3 (both versions take about 0.6 seconds).这种奇怪的情况在 Python 3 中不会发生(两个版本都需要大约 0.6 秒)。

Better optimization methods更好的优化方法

If you want to optimize your program, the best thing you can do is profile it .如果你想优化你的程序,你能做的最好的事情就是分析它 This will tell you what's taking the most time, so you can focus on that.这将告诉您什么花费最多时间,因此您可以专注于它。 Your process should be something like:你的过程应该是这样的:

  1. Run your program with profiling on.使用分析运行您的程序。
  2. Look at the profile in KCacheGrind or a similar program to determine Which functions are taking the most time.查看 KCacheGrind 或类似程序中的配置文件,以确定哪些函数占用的时间最多。
  3. In those functions:在这些函数中:
    • Look for places where you can cache results of functions (so you don't have to do as much work).寻找可以缓存函数结果的地方(这样您就不必做太多的工作)。
    • Look for algorithmic improvements like replacing recursive functions with closed-form functions, or replacing list searches with dictionaries.寻找算法改进,例如用闭式函数替换递归函数,或用字典替换列表搜索。
    • Re-profile to make sure the function is still a problem.重新配置文件以确保该功能仍然存在问题。
    • Consider using multiprocessing .考虑使用multiprocessing

Simple Answer:简单回答:

Due to Python's dynamic nature, when the interpreter comes across an expression like abc, it looks up a (trying first the local namespace, then the global namespace, and finally the built-in namespace), then it looks in that object's namespace to resolve the name b, and finally it looks in that object's namespace to resolve the name c.由于 Python 的动态特性,当解释器遇到像 abc 这样的表达式时,它会查找 a(首先尝试本地名称空间,然后是全局名称空间,最后是内置名称空间),然后在该对象的名称空间中查找以解析名称 b,最后在该对象的名称空间中查找以解析名称 c。 These lookups are reasonably fast;这些查找相当快; For local variables, lookups are extremely fast, since the interpreter knows which variables are local and can assign them a known position in memory.对于局部变量,查找速度非常快,因为解释器知道哪些变量是局部变量,并且可以为它们分配内存中的已知位置。

Interpreter knows which names inside your functions are local and it assigns them specific (known) locations inside the function call's memory.解释器知道函数中的哪些名称是本地的,并为它们分配函数调用内存中的特定(已知)位置。 This makes references to locals much faster than to globals and (most especially) to built-ins.这使得对局部变量的引用比对全局变量和(尤其是)对内置函数的引用要快得多。

Code example to explain the same:解释相同的代码示例:

>>> 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
>>> 

When Python compiles a function, the function knows before it is called if the variables in it are locals, closures, or globals.当 Python 编译一个函数时,该函数在被调用之前就知道其中的变量是局部变量、闭包变量还是全局变量。

We have several ways of referencing variables in functions:我们有几种在函数中引用变量的方法:

  • globals全局变量
  • closures关闭
  • locals当地人

So let's create these kinds of variables in a few different functions so we can see for ourselves:因此,让我们在几个不同的函数中创建这些类型的变量,以便我们自己看看:

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拆解

We can see that each function knows where to look up the variable - it doesn't need to do so at runtime:我们可以看到每个函数都知道在哪里查找变量——它不需要在运行时这样做:

>>> 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

We can see that currently the byte-code for a global is LOAD_GLOBAL , a closure variable is LOAD_DEREF , and a local is LOAD_FAST .我们可以看到当前全局的字节码是LOAD_GLOBAL ,闭包变量是LOAD_DEREF ,局部是LOAD_FAST These are implementation details of CPython, and may change from version to version - but it is useful to be able to see that Python treats each variable lookup differently.这些是 CPython 的实现细节,可能会因版本而异——但能够看到 Python 以不同方式处理每个变量查找是很有用的。

Paste into an interpreter and see for yourself:粘贴到解释器中并亲自查看:

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

Test code测试代码

Test code (feel free to test on your system):测试代码(随意在您的系统上测试):

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

Output输出

On Windows, at least in this build, it looks like closures get a little bit of a penalty - and using a local that's a default is the fastest, because you don't have to assign the local each time:在 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

On Linux:在 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

I'll add other systems as I have a chance to test them.我会添加其他系统,因为我有机会测试它们。

The time that you are not including is the programmer time spent tracking down the bugs created when using a global has a side effect somewhere else in your program.您不包括的时间是程序员在跟踪使用全局变量时创建的错误所花费的时间在程序中的其他地方有副作用。 That time is many times greater than the time spent creating and freeing local variables,那个时间比创建和释放局部变量所花费的时间长很多倍,

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM