当前位置:  开发笔记 > 编程语言 > 正文

为什么无参数函数调用执行得更快?

如何解决《为什么无参数函数调用执行得更快?》经验,为你挑选了1个好方法。

我设置了一个简单的自定义函数,它接受一些默认参数(Python 3.5):

def foo(a=10, b=20, c=30, d=40):
    return a * b + c * d

并且在有或没有指定参数值的情况下定时调用它:

没有指定参数:

%timeit foo()
The slowest run took 7.83 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 361 ns per loop

指定参数:

%timeit foo(a=10, b=20, c=30, d=40)
The slowest run took 12.83 times longer than the fastest. This could mean that an intermediate result is being cached 
1000000 loops, best of 3: 446 ns per loop

正如您所看到的,指定参数的调用和未指定参数的调用所需的时间会有一些明显的增加.在简单的一次性调用中,这可能是微不足道的,但是如果对函数进行大量调用,则开销会变得更加明显:

没有参数:

%timeit for i in range(10000): foo()
100 loops, best of 3: 3.83 ms per loop

随着参数:

%timeit for i in range(10000): foo(a=10, b=20, c=30, d=40)
100 loops, best of 3: 4.68 ms per loop

同样的情况存在,且在Python 2.7,其中这些调用之间的时间差实际上是一个有点大foo() -> 291nsfoo(a=10, b=20, c=30, d=40) -> 410ns


为什么会这样?我通常应该尝试避免在调用期间指定参数值吗?



1> Jim Fasaraki..:

为什么会这样?我应该避免在通话期间指定参数值吗?

一般来说,没有.您能够看到这个的真正原因是因为您使用的功能根本不是计算密集型的.这样,可以通过定时检测在提供自变量的情况下发出的附加字节代码命令所需的时间.

例如,如果您具有更强大的表单功能:

def foo_intensive(a=10, b=20, c=30, d=40): 
    [i * j for i in range(a * b) for j in range(c * d)]

它几乎没有显示出所需的时间差异:

%timeit foo_intensive()
10 loops, best of 3: 32.7 ms per loop

%timeit foo_intensive(a=10, b=20, c=30, d=40)
10 loops, best of 3: 32.7 ms per loop

即使缩放到更多调用,执行函数体所需的时间也只是胜过附加字节码指令引入的小开销.


看看字节代码:

查看为每个调用案例发出的生成字节代码的一种方法是创建一个包装foo并以不同方式调用它的函数.现在,让我们fooDefault使用默认参数和fooKwargs()指定关键字参数的函数创建调用:

# call foo without arguments, using defaults
def fooDefault():
    foo()

# call foo with keyword arguments
def fooKw():
    foo(a=10, b=20, c=30, d=40)

现在dis我们可以看到它们之间的字节代码差异.对于默认的版本中,我们可以看到,基本上是一个命令发出(忽略POP_TOP这是目前在这两种情况下)的函数调用,CALL_FUNCTION:

dis.dis(fooDefaults)
  2           0 LOAD_GLOBAL              0 (foo)
              3 CALL_FUNCTION            0 (0 positional, 0 keyword pair)  
              6 POP_TOP
              7 LOAD_CONST               0 (None)
             10 RETURN_VALUE

另一方面,在使用关键字的情况下,为了将参数名称和值加载到值堆栈中,还发出8个LOAD_CONST命令(即使在这种情况下加载数字可能非常快,因为它们被缓存):(a, b, c, d)(10, 20, 30, 40)< 256

dis.dis(fooKwargs)
  2           0 LOAD_GLOBAL              0 (foo)
              3 LOAD_CONST               1 ('a')    # call starts
              6 LOAD_CONST               2 (10)
              9 LOAD_CONST               3 ('b')
             12 LOAD_CONST               4 (20)
             15 LOAD_CONST               5 ('c')
             18 LOAD_CONST               6 (30)
             21 LOAD_CONST               7 ('d')
             24 LOAD_CONST               8 (40)
             27 CALL_FUNCTION         1024 (0 positional, 4 keyword pair)
             30 POP_TOP                             # call ends
             31 LOAD_CONST               0 (None)
             34 RETURN_VALUE

此外,对于关键字参数不为零的情况,通常需要一些额外的步骤.(例如ceval/_PyEval_EvalCodeWithName()).

即使这些是非常快的命令,但它们总结了.越多的参数越大,并且当实际执行对函数的许多调用时,这些参数堆积起来导致执行时间的感觉差异.


这些的直接结果是我们指定的值越多,必须发出的命令越多,函数运行得越慢.此外,指定位置参数,解压缩位置参数和解压缩关键字参数都具有与之关联的不同开销量:

    位置参数foo(10, 20, 30, 40):需要4个附加命令来加载每个值.

    列表解包foo(*[10, 20, 30, 40]):4个LOAD_CONST命令和一个附加BUILD_LIST命令.

    使用列表可以foo(*l)减少执行,因为我们提供了一个包含值的已构建列表.

    字典解包foo(**{'a':10, 'b':20, 'c': 30, 'd': 40}):8个LOAD_CONST命令和a BUILD_MAP.

    与列表一样,解压缩foo(**d)将减少执行,因为将提供内置列表.

总而言之,不同呼叫情况的执行时间顺序为:

defaults < positionals < keyword arguments < list unpacking < dictionary unpacking

我建议使用dis.dis这些案例,看看他们的差异.


结论:

正如@goofd在评论中指出的那样,这确实是一个不应该担心的事情,它确实取决于用例.如果您经常从计算角度调用'轻型'功能,则指定默认值会略微提高速度.如果您经常提供不同的值,则几乎不会产生任何值.

因此,它可能是微不足道的,并试图从这样的模糊边缘案例中获得提升,这真的是推动它.如果你发现自己这样做,你可能想看看像PyPy和的东西Cython.


好的.然而有两件事1)理想情况下你应该对函数声明和用法进行计时.默认值需要更多时间进行初始化.2)它也是关于用例的.如果您打算在大多数时间使用默认值,则声明默认值最佳.但是,如果例如a可以采用多个值,则声明a的默认值是非常无用的
推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有