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

时间:2016-07-15 13:50:23

标签: python performance runtime

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

函数1执行并且是自包含的,执行TaskA。

f1:
  For x in X:
    do task a

功能2执行并调用功能3.功能3执行TaskA

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

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

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

1 个答案:

答案 0 :(得分:1)

另一个因素可能是"准备" / setup在调用internal之前完成。可能在TaskA您已经在f1循环之前完成了一次,然后在for中完成了它,因此每个f3都会调用它x in X而不是一开始就是一次。没有任何真实的代码,很难说。

至于为每个f2调用f3的潜在复杂性,这不太可能导致10x缓慢。

只有在使用x的过于简单的示例中,我们才会看到此行为。让我们来看看passf1f2的这3个不良版本:

f3

使用>>> def f1(): ... for x in X: ... pass ... >>> def f2(): ... for x in X: ... f3() ... >>> def f3(): ... pass ... ,这里是dis的字节码:

f1

... vs >>> 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_FUNCTION之外,它们看起来几乎相同。但是,它们与POP_TOP非常不同:

timeit

现在已经8倍了,但不是因为调用函数,而是因为在>>> X = range(1000) # [0, 1, 2, ...999] >>> >>> import timeit >>> timeit.timeit(f1) 10.290941975496747 >>> timeit.timeit(f2) 81.18860785875617 >>> 的{​​{1}}中只做了pass for循环是非常快,尤其是每次调用函数然后什么都不做的时候。所以希望你使用这些作为例子来找出/想知道为什么。

现在,如果您在任务中实际做某事,比如说f1,那么您会看到两者之间的时间/性能差异变得更小:

x * x

现在只有 2.9x 的时间。它不是导致缓慢的函数调用(是的,有一些开销),而是你在该函数中所做的事情与>>> 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 >>> 相比,这会对总体时间产生影响。

如果您在两个地方都将pass替换为_ = x * x,这是相当"慢",只有print x * x

X = range(5)

现在他们的表现差别要小得多。

所以用实际代码进行实际检查,而不仅仅是简单的伪代码分析。空调用可能看起来更快,但与函数中的代码较慢的东西相比,开销非常小。