从python的运行时效率角度来看,这些效率是否相同?
x = foo()
x = bar(x)
VS
x = bar(foo())
我有一个更复杂的问题,基本上可以归结为这个问题:显然,从代码长度的角度来看,第二个更为有效,但运行时是否也更好?如果不是,为什么不呢?
答案 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))
观察到,每次迭代,差异都在减小。此图表明 性能差异不明显 。从可读性的角度来看,第二种情况看起来更好。
在第一种情况下,将评估两个表达式:第一个表达式首先将返回值从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 to和reading from是C数组中编译时确定的插槽,外加一个整数增量来调整引用计数。您需要支付每个字节代码所需的CPython解释器开销,但是实际工作的成本微不足道。
要点是:不要担心速度,编写任何可读性和可维护性更高的代码版本。在这种情况下,所有名称都是垃圾,但是如果输出来自可以给foo
赋予一个有用的名称,然后传递给bar
,其输出被赋予另一个有用的名称,如果没有这些名称,则foo
和bar
之间的关系是非-显而易见,请勿内联。如果关系很明显,并且foo
的输出不会从命名中受益,请对其进行内联。避免局部变量的存储和加载是微观优化的最微妙之处。几乎在任何情况下,它都不是造成有意义的性能损失的原因,因此,请勿在此基础上进行代码设计决策。