Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/285.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 为什么无参数函数调用执行得更快?_Python_Python 2.7_Function_Python 3.x_Python Internals - Fatal编程技术网

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

Python 为什么无参数函数调用执行得更快?,python,python-2.7,function,python-3.x,python-internals,Python,Python 2.7,Function,Python 3.x,Python Internals,我设置了一个简单的自定义函数,它接受一些默认参数(Python 3.5): 以及在指定参数值或不指定参数值的情况下对其进行不同调用的时间: 不指定参数: %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

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

以及在指定参数值或不指定参数值的情况下对其进行不同调用的时间:

不指定参数

%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(a=10, b=20, c=30, d=40)
100 loops, best of 3: 4.68 ms per loop
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
指定参数

%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(a=10, b=20, c=30, d=40)
100 loops, best of 3: 4.68 ms per loop
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
如您所见,指定参数的调用和未指定参数的调用所需的时间都有明显的增加。在简单的一次性调用中,这可能可以忽略不计,但如果对函数进行了大量调用,则开销会增加并变得更加明显:

无参数

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

%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(a=10, b=20, c=30, d=40)
100 loops, best of 3: 4.68 ms per loop
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
Python2.7中也存在同样的行为,在Python2.7中,这些调用之间的时间差实际上稍大一些
foo()->291ns
foo(a=10,b=20,c=30,d=40)->410ns


为什么会发生这种情况?我是否应该在调用过程中尽量避免指定参数值

为什么会发生这种情况?我应该避免在调用期间指定参数值吗

一般来说,没有。您能够看到这一点的真正原因是您使用的函数不是计算密集型的。因此,在提供参数的情况下发出附加字节码命令所需的时间可以通过计时来检测

例如,如果您有一个更密集的表单功能:

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
的函数,并以不同的方式调用它。现在,让我们为使用默认参数的调用创建
foodault
,为指定关键字参数的函数创建
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)
现在,通过我们可以看到它们之间的字节码差异。对于默认版本,我们可以看到基本上只为函数调用发出了一个命令(忽略在两种情况下都存在的
POP_TOP

%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(a=10, b=20, c=30, d=40)
100 loops, best of 3: 4.68 ms per loop
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个命令,以便将参数名
(a、b、c、d)
和值
(10、20、30、40)
加载到值堆栈中(尽管在这种情况下加载数字
<256
可能非常快,因为它们是缓存的):

此外,对于关键字参数不为零的情况,通常需要执行一些额外的步骤。(例如在中)

尽管这些命令非常快,但它们确实是总结。参数越多,总和就越大,而且当实际执行了许多函数调用时,这些参数会堆积起来,从而导致执行时间上的明显差异


这样做的一个直接结果是,我们指定的值越多,必须发出的命令越多,函数运行得越慢。此外,指定位置参数、解包位置参数和解包关键字参数都有不同的开销:

  • 位置参数
    foo(10,20,30,40)
    需要4个附加命令来加载每个值
  • 列出解包
    foo(*[10,20,30,40])
    :4
    加载常量
    命令和附加的命令。
    • 使用
      foo(*l)
      中的列表可以稍微减少执行,因为我们提供了一个包含值的已构建列表
  • 字典解包
    foo(**{'a':10,'b':20,'c':30,'d':40})
    :8
    加载常量命令和a
    • 与列表解包一样,
      foo(**d)
      将减少执行,因为将提供生成的列表
  • 总而言之,不同调用情况下的执行时间顺序为:

    defaults < positionals < keyword arguments < list unpacking < dictionary unpacking
    
    defaults
    我建议在这些情况下使用
    dis.dis
    ,看看它们的区别


    总之: 正如@goofd在评论中指出的,这确实是一件不应该担心的事情,它确实取决于用例。如果您经常从计算的角度调用“light”函数,那么指定默认值将略微提高速度。如果经常提供不同的值,则几乎不会产生任何结果


    所以,它可能是可以忽略不计的,试图从这种模糊的边缘案例中获得提升,这真的是在推动它。如果您发现自己正在这样做,您可能希望看到像and这样的东西。

    好的ans。但是有两件事1)理想情况下,您应该对函数声明+用法计时。默认值需要更多的时间来初始化。2) 它还与用例有关。如果您打算大部分时间使用默认值,那么声明默认值是最好的。但是,例如,如果a可以接受多个值,那么为a声明默认值是非常有用的。如果您不为
    foodaults
    提供参数或值,那么它实际上与
    fooKwargs
    做的事情就不一样了;因此,您最好不要使用任何参数,只需执行
    返回10*20+30*40
    谢谢您的反馈@goofd我将编辑第二点,并在稍后添加它,因为用例在这里非常重要。有趣的是,在我的机器上,使用位置参数调用的无默认函数与仅使用默认值调用的默认函数一样快。