当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《全局与本地命名空间性能差异》经验,为你挑选了1个好方法。

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

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

在python中运行速度比在顶层执行命令1.5x3x快得多:

[do stuff]
print(something)

Jim Fasaraki.. 14

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

在函数中,用于加载/存储名称的字节代码用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/]

这里要注意的一点是LOAD_FAST/STORE_FAST指令的偏移2832,这些都是用来访问b中所使用的名称BINARY_MULTIPLY操作和储存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,如前所述,这些调用是更缓慢的命令.

在这种情况下,存在将是在执行时间具有明显的差异,主要是因为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()大于*_FASTvs *_NAME差异引入的开销.所以它在很大程度上取决于完成的工作量.

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


您可以查看为每个字节代码执行的代码.我会在3.5这里链接Python版本的源代码,尽管我很确定2.7并没有多大差别.字节码评估Python/ceval.c专门在功能上完成PyEval_EvalFrameEx:

LOAD_FAST source - STORE_FAST source

LOAD_NAME source - STORE_NAME source

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



1> Jim Fasaraki..:

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

在函数中,用于加载/存储名称的字节代码用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/]

这里要注意的一点是LOAD_FAST/STORE_FAST指令的偏移2832,这些都是用来访问b中所使用的名称BINARY_MULTIPLY操作和储存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,如前所述,这些调用是更缓慢的命令.

在这种情况下,存在将是在执行时间具有明显的差异,主要是因为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()大于*_FASTvs *_NAME差异引入的开销.所以它在很大程度上取决于完成的工作量.

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


您可以查看为每个字节代码执行的代码.我会在3.5这里链接Python版本的源代码,尽管我很确定2.7并没有多大差别.字节码评估Python/ceval.c专门在功能上完成PyEval_EvalFrameEx:

LOAD_FAST source - STORE_FAST source

LOAD_NAME source - STORE_NAME source

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

推荐阅读
凹凸曼00威威_694
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有