为什么在函数中执行一组命令:
def main(): [do stuff] return something print(main())
在python中运行速度比在顶层执行命令1.5x
要3x
快得多:
[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
指令的偏移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/]
在这里,我们有多个调用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
将执行时间定位为函数:
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
差异引入的开销.所以它在很大程度上取决于完成的工作量.
所以,这里真正的罪魁祸首,以及这种差异显而易见的原因是使用的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
包含在框架对象内的本地符号表来获取存储/加载的值.
差异确实很大程度上取决于"做事"实际上做了什么,主要取决于它访问定义/使用的名称的次数.假设代码类似,这两种情况之间存在根本区别:
在函数中,用于加载/存储名称的字节代码用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
指令的偏移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/]
在这里,我们有多个调用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
将执行时间定位为函数:
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
差异引入的开销.所以它在很大程度上取决于完成的工作量.
所以,这里真正的罪魁祸首,以及这种差异显而易见的原因是使用的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
包含在框架对象内的本地符号表来获取存储/加载的值.