So I wrote some code for a max/min that assumes a bottleneck on generating the data(否则我只使用max
和min
),它需要一个关键函数,如果没有给出使用标识函数:
if key is None:
key = lambda x: x
然后再说:
for i in iterable:
key_i = key(i)
由于瓶颈在发电机上,问题可能没有实际意义,但如果没有钥匙,我会为每件物品拨打lambda x: x
。 我认为Python可以优化此身份功能。有人可以告诉我它是否存在?或者如果没有,那它有多贵?有没有办法在不增加行数的情况下更好地完成这项工作(例如三元运算符)?
答案 0 :(得分:8)
好问题!优化器可以看到foo在某些可预测的条件下可能是一个标识函数,并创建一个替代路径来替换它的已知结果的调用
>>> def foo(n):
... f = lambda x:x
... return f(n)
...
>>> import dis
>>> dis.dis(foo)
2 0 LOAD_CONST 1 (<code object <lambda> at 0x7f177ade7608, file "<stdin>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 1 (f)
3 9 LOAD_FAST 1 (f)
12 LOAD_FAST 0 (n)
15 CALL_FUNCTION 1
18 RETURN_VALUE
CPython(2.7和3.3测试)似乎没有优化lambda调用。也许另一种实现呢?
>>> dis.dis(lambda x:x)
1 0 LOAD_FAST 0 (x)
3 RETURN_VALUE
身份功能没有太大作用。因此,每次调用身份函数时,基本上都有2个LOAD_FAST,1个CALL_FUNCTION和1个RETURN_VALUE,而不是创建一个可靠的替代路径(可能比@viraptor所说的解释器更复杂)。
也许python代码中的else路径更好。
您在min / max示例中所做的真正优化是通过存储函数的结果来减少函数的调用次数。它现在被称为n次而不是n * 4,这是一个公平的收益!
答案 1 :(得分:3)
不幸的是,lambda x: x
刚刚创建了一些功能,从外部看 - 我们不知道它的作用。当然,在那时,我们理论上可以意识到它只是一个身份函数,使其计算相当多余。但即便如此,我们只是将这个函数存储在一个变量中,现在就用它来完成。
然后,我们调用名称,执行底层函数。因为它是一个函数,我们对函数一无所知,所以我们无法分辨它的作用,所以我们只需要执行它。从技术上讲,优化器可以看到它是一个标识函数并跳过调用,直接返回值,但在Python中很难做到这一点。 Peephole优化器在看到一些可能性时已经取出了一些字节码指令,但在这种情况下,这很难做到:
名称调用通常为LOAD_FAST
,后跟参数加载,然后是CALL_FUNCTION
。这紧跟在something(args)
的语法之后。因此,理论上的优化器必须跳过第一个加载和调用函数。但是,即使考虑到这一点,它也必须知道首先加载的名称是指身份函数。
现在,Peephole优化器的工作方式,它不适用于动态变量内容。即使我们有一些标志我们可以附加到函数,所以我们可以快速检查它是否是一个标识函数,优化器仍然无法读取它,因为它不对底层数据进行操作。它仅对字节码操作进行操作,将LOAD_GLOBAL True
等内容减少为LOAD_CONST True
。
说实话,为身份功能引入这样的标志会相当奇怪。身份功能已经很少见;如果我们要优化它,我们也可以内联所有lambda,并完全减少函数调用的开销。但这并不是Peephole优化器或解释语言的任何(?)优化器所做的。在运行期间的开销可能只是太大而且会对微优化的整体性能产生负面影响。
因为通常情况下,这样的优化水平根本不值得。这样的函数调用很少成为您的应用程序的瓶颈,如果是这样,您将不得不考虑以不同方式对其进行优化。
答案 2 :(得分:3)
这里还没有人做过性能检查,所以我认为这可能是更好的选择:使用三元运算或单独的控制流程(我非常确定分离控制流程将是最佳解决方案)。
在Python 3中测试它们:
import timeit
setup = """
def control_flow(iterable, key=None):
if key is None:
for i in iterable:
pass
else:
for i in iterable:
key_i = key(i)
def identity_lambda(iterable, key=None):
if key is None:
key = lambda x: x
for i in iterable:
key_i = key(i)
def ternary(iterable, key=None):
for i in iterable:
key_i = key(i) if key else i
"""
print('Testing no lambda')
timeit.timeit('control_flow(range(100))', setup=setup)
timeit.timeit('identity_lambda(range(100))', setup=setup)
timeit.timeit('ternary(range(100))', setup=setup)
print('Testing with lambda')
timeit.timeit('control_flow(range(100), lambda x: -x)', setup=setup)
timeit.timeit('identity_lambda(range(100), lambda x: -x)', setup=setup)
timeit.timeit('ternary(range(100), lambda x: -x)', setup=setup)
结果如下:
Testing no lambda
1.8421741100028157
10.212458187001175
3.39080909700715
Testing with lambda
14.262093641998945
14.405747531011002
14.198169080002117
所以我认为最好的选择是分离控制流,有效地将每个分支下的代码加倍,而不是使用lambda x: x
,至少在这种情况下。我认为最重要的是在这里可以学到的是Python并没有针对身份功能进行优化,有时可以对更多代码行进行性能改进,但代价是可维护性较低,错误的可能性较大。
答案 3 :(得分:1)
CPython没有对此进行优化。怎么可能呢?在它被调用之前,它无法知道这是一个身份函数。