繁体   English   中英

全局与本地命名空间性能差异

[英]global vs. local namespace performance difference

为什么在函数中执行一组命令:

def main():
    [do stuff]
    return something
print(main())

在python中运行速度比在顶级执行命令要快1.5x3x倍:

[do stuff]
print(something)

差异确实很大程度上取决于“做事”实际上做了什么, 主要取决于它访问定义/使用的名称的次数。 假设代码类似,这两种情况之间存在根本区别:

  • 在函数中,使用LOAD_FAST / STORE_FAST完成加载/存储名称的字节代码。
  • 在顶级范围(即模块)中,使用LOAD_NAME / STORE_NAME执行相同的命令,这些命令更加缓慢。

这可以在以下情况下查看, 我将使用for循环来确保定义的变量的查找多次执行

功能和LOAD_FAST/STORE_FAST

我们定义了一个简单的函数来做一些非常愚蠢的事情:

def main():
    b = 20
    for i in range(1000000): z = 10 * b 
    return z

dis.dis生成的输出:

dis.dis(main)
# [/snipped output/]

             18 GET_ITER
        >>   19 FOR_ITER                16 (to 38)
             22 STORE_FAST               1 (i)
             25 LOAD_CONST               3 (10)
             28 LOAD_FAST                0 (b)
             31 BINARY_MULTIPLY
             32 STORE_FAST               2 (z)
             35 JUMP_ABSOLUTE           19
        >>   38 POP_BLOCK

# [/snipped output/]

这里要注意的是偏移2832处的LOAD_FAST/STORE_FAST命令,这些命令用于访问BINARY_MULTIPLY操作中使用的b名称并分别存储z名称。 正如它们的字节代码名称所暗示的那样, 它们是 LOAD_*/STORE_*系列的快速版本


模块和LOAD_NAME/STORE_NAME

现在,让我们看看上一个函数的模块版本的dis输出:

# compile the module
m = compile(open('main.py', 'r').read(), "main", "exec")

dis.dis(m)
# [/snipped output/]

             18 GET_ITER
        >>   19 FOR_ITER                16 (to 38)
             22 STORE_NAME               2 (i)
             25 LOAD_NAME                3 (z)
             28 LOAD_NAME                0 (b)
             31 BINARY_MULTIPLY
             32 STORE_NAME               3 (z)
             35 JUMP_ABSOLUTE           19
        >>   38 POP_BLOCK

# [/snipped output/]

在这里,我们有多个LOAD_NAME/STORE_NAME调用, LOAD_NAME/STORE_NAME这些调用执行起来比较迟钝

在这种情况下, 执行时间会有明显的差异 ,主要是因为Python必须多次评估LOAD_NAME/STORE_NAMELOAD_FAST/STORE_FAST (由于我添加了for循环),因此每次都会引入开销执行每个字节代码的代码将累积

将执行“定位为模块”:

start_time = time.time()
b = 20 
for i in range(1000000): z = 10 *b
print(z)
print("Time: ", time.time() - start_time)
200
Time:  0.15162253379821777

将执行时间定位为函数:

start_time = time.time()
print(main())
print("Time: ", time.time() - start_time)
200
Time:  0.08665871620178223 

如果你time在一个较小的循环range (例如for i in range(1000)你会发现,“模块”的版本速度更快。 这是因为需要调用函数main()引入的开销大于*_FAST vs *_NAME *_FAST差异引入的*_FAST 所以它在很大程度上取决于完成的工作量。

所以,这里真正的罪魁祸首,以及这种差异显而易见的原因是使用了for循环。 你通常有0理由在脚本的顶层放置一个密集的循环。 在函数中移动它并避免使用全局变量 ,它被设计为更高效。


您可以查看为每个字节代码执行的代码。 我会在这里链接3.5版Python的源代码,尽管我很确定2.7没有太大差别。 字节码评估在Python/ceval.c完成,特别是在函数PyEval_EvalFrameEx

正如您将看到的, *_FAST字节码只是使用框架对象中包含fastlocals本地符号表来获取存储/加载的值。

暂无
暂无

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

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