函数调用执行速度比非函数调用快
Function call execution speed is faster than non-function call
函数调用总会产生一些开销。但是为什么下面的代码显示非函数调用比较慢。
代码:
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
您可能会想,既然循环只执行 1 + 1
,那么应该没有太大区别。 但是,这里有一个通常被遗忘的'hidden' assignment:到for
循环中的循环变量i
。这就是减速的原因。
在函数中,这是用 STORE_FAST
. In the top level, it's done with 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])
)
函数调用总会产生一些开销。但是为什么下面的代码显示非函数调用比较慢。
代码:
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
您可能会想,既然循环只执行 1 + 1
,那么应该没有太大区别。 但是,这里有一个通常被遗忘的'hidden' assignment:到for
循环中的循环变量i
。这就是减速的原因。
在函数中,这是用 STORE_FAST
. In the top level, it's done with 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])
)