use*_*264 8 python optimization python-2.7 python-3.x
为什么在函数中执行一组命令:
def main():
[do stuff]
return something
print(main())
Run Code Online (Sandbox Code Playgroud)
在python中运行速度比在顶层执行命令1.5x要3x快得多:
[do stuff]
print(something)
Run Code Online (Sandbox Code Playgroud)
Jim*_*ard 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
Run Code Online (Sandbox Code Playgroud)
产生的输出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/]
Run Code Online (Sandbox Code Playgroud)
这里要注意的一点是LOAD_FAST/STORE_FAST指令的偏移28和32,这些都是用来访问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/]
Run Code Online (Sandbox Code Playgroud)
在这里,我们有多个调用LOAD_NAME/STORE_NAME,如前所述,这些调用是更缓慢的命令.
在这种情况下,存在将是在执行时间具有明显的差异,主要是因为Python必须评估LOAD_NAME/STORE_NAME和LOAD_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
Run Code Online (Sandbox Code Playgroud)
将执行时间定位为函数:
start_time = time.time()
print(main())
print("Time: ", time.time() - start_time)
200
Time: 0.08665871620178223
Run Code Online (Sandbox Code Playgroud)
如果你time循环一个较小的range(例如for i in range(1000)),你会注意到'模块'版本更快.这是因为需要调用函数引入的开销main()大于*_FASTvs *_NAME差异引入的开销.所以它在很大程度上取决于完成的工作量.
所以,这里真正的罪魁祸首,以及这种差异显而易见的原因是使用的for循环.您通常0有理由在脚本的顶层放置类似于密集循环的密集循环.在函数中移动它并避免使用全局变量,它被设计为更高效.
您可以查看为每个字节代码执行的代码.我会在3.5这里链接Python版本的源代码,尽管我很确定2.7并没有多大差别.字节码评估Python/ceval.c专门在功能上完成PyEval_EvalFrameEx:
正如您将看到的,*_FAST字节码只是使用fastlocals包含在框架对象内的本地符号表来获取存储/加载的值.