将分配分成两行是否仍然一样有效?

时间:2019-04-24 14:52:37

标签: python performance optimization local-variables

从python的运行时效率角度来看,这些效率是否相同?

x = foo()
x = bar(x)

VS

x = bar(foo())

我有一个更复杂的问题,基本上可以归结为这个问题:显然,从代码长度的角度来看,第二个更为有效,但运行时是否也更好?如果不是,为什么不呢?

2 个答案:

答案 0 :(得分:4)

这里是一个比较:

第一种情况

%%timeit
def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():
    x = foo()
    y = bar(x)
    return y

test()
#Output:
'foobar'
529 ns ± 114 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

第二种情况

%%timeit

def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():   
    x = bar(foo())
    return x

test()
#Output:
'foobar'
447 ns ± 34.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

但这只是针对每种情况运行一次%% timeit的比较。以下是每种情况下 20次迭代的时间(以ns为单位的时间):

df = pd.DataFrame({'First Case(time in ns)': [623,828,634,668,715,659,703,687,614,623,697,634,686,822,671,894,752,742,721,742], 
               'Second Case(time in ns)': [901,786,686,670,677,683,685,638,628,670,695,657,698,707,726,796,868,703,609,852]})

df.plot(kind='density', figsize=(8,8))

enter image description here

观察到,每次迭代,差异都在减小。此图表明 性能差异不明显 。从可读性的角度来看,第二种情况看起来更好。

在第一种情况下,将评估两个表达式:第一个表达式首先将返回值从foo()分配给x,然后第二个表达式对该值调用bar()。这增加了一些开销。在第二种情况下,仅对一个表达式求值,一次调用两个函数并返回值。

答案 1 :(得分:2)

这很重要,但意义不大。 amanb's test仅在一个测试中对函数的定义进行了计时,因此在第一个测试中必须做更多的工作,从而使结果产生偏差。经过正确测试,结果仅以最小的幅度差异。使用相同的ipython %%timeit魔术(IPython版本7.3.0,Linux x86-64的CPython版本3.7.2),但从每个循环测试中删除了函数的定义:

>>> def foo():
...     return "foo"
... def bar(text):
...     return text + "bar"
... def inline():
...     x = bar(foo())
...     return x
... def outofline():
...     x = foo()
...     x = bar(x)
...     return x
...

>>> %%timeit -r5 test = inline
... test()
...
...
332 ns ± 1.01 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)


>>> %%timeit -r5 test = outofline
... test()
...
...
341 ns ± 5.62 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

inline代码更快,但差异小于10 ns / 3%。进一步内联(使主体成为return bar(foo()))可以节省更多的微小,但同样,它毫无意义。

这也是您所期望的;存储和加载函数本地名称是CPython解释器可以执行的最便宜的操作,是函数is that outofline requires an extra STORE_FAST and LOAD_FAST之间的唯一区别(一个在另一个之后),并且这些指令在内部仅实现为assignment toreading from是C数组中编译时确定的插槽,外加一个整数增量来调整引用计数。您需要支付每个字节代码所需的CPython解释器开销,但是实际工作的成本微不足道。

要点是:不要担心速度,编写任何可读性和可维护性更高的代码版本。在这种情况下,所有名称都是垃圾,但是如果输出来自可以给foo赋予一个有用的名称,然后传递给bar,其输出被赋予另一个有用的名称,如果没有这些名称,则foobar之间的关系是非-显而易见,请勿内联。如果关系很明显,并且foo的输出不会从命名中受益,请对其进行内联。避免局部变量的存储和加载是微观优化的最微妙之处。几乎在任何情况下,它都不是造成有意义的性能损失的原因,因此,请勿在此基础上进行代码设计决策。