函数调用总会产生一些开销。但是为什么下面的代码显示非函数调用较慢。
import time
def s():
for i in range(1000000000):
1 + 1
t = time.time()
s()
print("Function call: " + str(time.time() - t))
t = time.time()
for i in range(1000000000):
1 + 1
print("Non function call: " + str(time.time() - t))
Function call: 38.39736223220825
Non function call: 60.33238506317139
答案 0 :(得分:11)
你可能会认为,因为循环只有1 + 1
,所以应该没有太大区别。 但是,这里有一个“隐藏的”assignment通常被遗忘:到i
循环中的循环变量for
。这是经济放缓的原因。
在函数中,这是通过STORE_FAST
完成的。在顶层,它使用STORE_NAME
完成。第一个比另一个快,并且在一个运行1000000000
次的循环中,这个差异非常清楚地显示出来。
请记住,只有的函数调用只发生一次。因此,它的开销在这种特定情况下并没有真正起作用。
除此之外,所有其他步骤都发生一次并且几乎相同。创建一个范围并抓取其迭代器,并为每次迭代加载常量2
。
您可以随时使用dis
module来检查为每个字节码生成的CPython字节码,如@Moses在评论中指出的那样。对于函数s
,您有:
dis.dis(s)
# snipped for brevity
>> 10 FOR_ITER 8 (to 20)
12 STORE_FAST 0 (i)
3 14 LOAD_CONST 3 (2)
16 POP_TOP
18 JUMP_ABSOLUTE 10
对于循环的顶级版本:
dis('for i in range(1000000000): 1+1')
# snipped for brevity
>> 10 FOR_ITER 8 (to 20)
12 STORE_NAME 1 (i)
14 LOAD_CONST 3 (2)
16 POP_TOP
18 JUMP_ABSOLUTE 10
这些之间的主要区别在于存储迭代值i
。在函数中,它更有效率。
要解决@Reblochon Masque(现已删除)的答案,在IPython单元格中与timeit
定时时,这两个答案似乎没有显示出差异。
timeit
次通过创建一个小函数(named inner
)来存储您传递的语句并在给定次数的执行中执行它们。如果你创建一个Timer
对象并查看它的src
属性,你可以看到这个(这没有记录,所以不要指望它总是在那里: - ):
from timeit import Timer
t = Timer('for i in range(10000): 1 + 1')
print(t.src)
这包含基本上定时的小功能。之前的print
来电打印:
def inner(_it, _timer):
pass
_t0 = _timer()
for _i in _it:
for i in range(10000): 1 + 1
_t1 = _timer()
return _t1 - _t0
因此,实际上,通过使用timeit
,您已经改变了执行i
查找的方式,因为它位于函数内部,它也使用STORE_FAST
。容易陷入困境!
(如果您不相信我,请参阅dis.dis(compile(t.src, '', 'exec').co_consts[0])
)