脚本 运行 循环中代码与被调用函数中代码的时间差

Script run time difference for code in loop vs code in called function

我运行 2个函数。它们都有用于执行指令的 for 循环。这两个功能完成相同的任务,但一个需要更长的时间。

函数 1 执行并自包含,执行任务 A。

f1:
  For x in X:
    do task a

Function 2执行并调用Function 3。Function 3执行TaskA

f2:
  For x in X:
    call function 3
f3:
  do task a

为什么函数 2 的执行时间通常是函数 1 的 10 倍?

编辑:之前的措辞让人们感到困惑。

另一个因素可能是在调用 TaskA 之前完成的 "preparation" / 设置。可能在 f1 中,您在 for 循环之前完成了一次,然后在 f3 中完成,因此它会被 f2 中的每个 x in X 调用而不是一开始就一次。没有任何真正的代码,这很难说。

至于为每个 x 调用 f3 的潜在复杂性,这不太可能是 10 倍慢的原因。

只有在 pass 的过度简化示例中,我们才会看到这种行为。让我们以 f1f2f3 的这 3 个错误版本为例:

>>> def f1():
...   for x in X:
...     pass
...
>>> def f2():
...   for x in X:
...     f3()
...
>>> def f3():
...   pass
...

使用 dis,下面是 f1 的字节码:

>>> dis.dis(f1)
  2           0 SETUP_LOOP              14 (to 17)
              3 LOAD_GLOBAL              0 (X)
              6 GET_ITER
        >>    7 FOR_ITER                 6 (to 16)
             10 STORE_FAST               0 (x)

  3          13 JUMP_ABSOLUTE            7
        >>   16 POP_BLOCK
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

...对比 f2

>>> dis.dis(f2)
  2           0 SETUP_LOOP              21 (to 24)
              3 LOAD_GLOBAL              0 (X)
              6 GET_ITER
        >>    7 FOR_ITER                13 (to 23)
             10 STORE_FAST               0 (x)

  3          13 LOAD_GLOBAL              1 (f3)
             16 CALL_FUNCTION            0
             19 POP_TOP
             20 JUMP_ABSOLUTE            7
        >>   23 POP_BLOCK
        >>   24 LOAD_CONST               0 (None)
             27 RETURN_VALUE

除了 CALL_FUNCTIONPOP_TOP 外,它们看起来几乎相同。但是,它们与 timeit:

有很大不同
>>> X = range(1000)  # [0, 1, 2, ...999]
>>>
>>> import timeit
>>> timeit.timeit(f1)
10.290941975496747
>>> timeit.timeit(f2)
81.18860785875617
>>>

现在是时间的 8 倍,但不是因为调用函数 而是因为在 f1 的 for 循环中只做 pass非常快,尤其是当每次调用一个函数时什么都不做。所以希望你 不是 使用这些作为例子来找到 out/wonder 为什么。

现在,如果你真的在任务中做了一些事情,比如说 x * x 那么你会发现两者之间的 timing/performance 差异变小了:

>>> def f1():
...   for x in X:
...     _ = x*x
...
>>> def f2():
...   for x in X:
...     _ = f3(x)  # didn't pass in `x` to `f3` in the previous example
...
>>> def f3(x):
...   return x*x
...
>>> timeit.timeit(f1)
38.76545268807092
>>> timeit.timeit(f2)
113.72242594670047
>>>

现在只有 2.9x 时间了。导致速度缓慢的不是函数调用(是的,有一些开销),而是你在该函数中所做的与 pass 相比,这对总时间产生了影响。

如果你把两个地方的 _ = x * x 都替换成 print x * x,这相当于 "slow",而只用 X = range(5):

>>> timeit.timeit(f1, number=10000)
3.640433839719143
>>> timeit.timeit(f2, number=10000)
3.6921612171574765

现在他们的表现差异小了很多。

因此,请使用真实代码进行实际检查,而不仅仅是简单的伪代码分析。空调用可能看起来更快,但与函数中的代码较慢的东西相比,这种开销确实很小。