函数调用执行速度比非函数调用快

car*_*995 3 python function python-3.x python-internals

函数调用总会产生一些开销.但为什么下面的代码显示非函数调用较慢.

码:

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))
Run Code Online (Sandbox Code Playgroud)

输出:

Function call: 38.39736223220825
Non function call: 60.33238506317139
Run Code Online (Sandbox Code Playgroud)

Jim*_*ard 11

你可能会认为,因为循环只是这样1 + 1,所以应该没有太大区别.但是,这里有一个"隐藏"的赋值,它通常被遗忘:循环i中的循环变量for.这是经济放缓的原因.

在函数中,这是完成的STORE_FAST.在顶层,完成了STORE_NAME.第一个比另一个快,并且在一个运行1000000000时间的循环中,这个差异非常清楚地显示出来.

请记住,函数调用只发生一次.因此,它的开销在这种特定情况下并没有真正贡献.

除此之外,所有其他步骤只发生一次并且几乎相同.创建一个范围并抓取其迭代器,并2为每次迭代加载常量.


您始终可以使用该dis模块检查为每个生成的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
Run Code Online (Sandbox Code Playgroud)

而对于循环的顶级版本:

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
Run Code Online (Sandbox Code Playgroud)

它们之间的主要区别在于存储迭代值i.在功能方面,它更有效率.


为了解决@Reblochon Masque(现已删除)的答案,这似乎表明这两者timeit在IPython单元格中时没有差异.

timeit通过创建一个小函数(命名inner)来计算事物,该函数存储您传递的语句并为给定数量的执行执行它们.你可以看到这个,如果你创建一个Timer对象并查看它的src属性(这没有记录,所以不要指望它总是在那里:-):

from timeit import Timer

t = Timer('for i in range(10000): 1 + 1')
print(t.src)
Run Code Online (Sandbox Code Playgroud)

这包含基本上定时的小功能.上一个print电话打印:

def inner(_it, _timer):
    pass
    _t0 = _timer()
    for _i in _it:
        for i in range(10000): 1 + 1
    _t1 = _timer()
    return _t1 - _t0
Run Code Online (Sandbox Code Playgroud)

所以,实际上,通过使用timeit你已经改变了执行查找的方式i,因为它在函数内部也是如此STORE_FAST.容易陷入困境!

(如果你不相信我,请看dis.dis(compile(t.src, '', 'exec').co_consts[0]))