python - Python 列表理解的性能 : rebuilding lambda in `if` part?

标签 python performance list-comprehension

在比较 Python 中 filter(xs, lambda x: x != el) 的各种等效形式时,我偶然发现了一些让我感到惊讶的事情。考虑以下形式:

def method1(xs, el):
    p = lambda x: x != el
    return [x for x in xs if p(x)]

def method2(xs, el):
    return [x for x in xs if (lambda y: y != el)(x)]

我希望 Python 只构建一次 lambda,然后将其存储在一个临时变量中,这样两种形式的性能都差不多。由于名称查找,甚至 method1 的性能可能更差。

但是当我对它们进行基准测试时,结果证明 method2 的性能始终比 method1 差。为什么是这样?是否为每次迭代重建 lambda?


我的基准测试脚本(在一个单独的模块中,并期望 methods 包含 method1method2)如下:

import math, timeit

def bench(n,rho,z):
    pre = """\
import random
from methods import %(method)s

x = [(random.randint(0,%(domain)i)) for r in xrange(%(size)i)]
el = x[0]\
"""

    def testMethod(m):
        mod = pre % { 'method': m, 'domain': int(math.ceil(n / rho)), 'size': n }
        return timeit.timeit("%s(x, el)" % m, mod, number = z)/(z * n)

    print "Testing", n, rho, z
    return tuple(testMethod(m) for m in ("method1", "method2"))

n = 31

min_size, max_size = 10.0**1, 10.0**4
size_base = math.pow(max_size / min_size, 1.0/(n-1))
# size_default = 10**3

#min_sel, max_sel = 0.001, 1.0
#sel_base = math.pow(max_sel / min_sel, 1.0/(n-1))
sel_default = 0.001

tests = [bench(int(min_size*size_base**x), sel_default, 100) for x in xrange(n)]
#tests = [bench(size_default, min_sel*sel_base**x, 100) for x in xrange(n)]

def median(x):
    x = list(sorted(x))
    mi = int(len(x)/2)
    if n % 2 == 0:
        return x[mi]
    else:
        return (x[mi] + x[mi+1])/2

def madAndMedian(x):
    meh = median(x)
    return meh, median([abs(xx - meh) for xx in x])

for z in zip(*tests):
    print madAndMedian(z)

最佳答案

是的,它在每个循环中重建 lambda;它需要重新评估整个表达式。

要查看此内容,请使用 dis module :

>>> dis.dis(method1)
  2           0 LOAD_CLOSURE             0 (el)
              3 BUILD_TUPLE              1
              6 LOAD_CONST               1 (<code object <lambda> at 0x102000230, file "<stdin>", line 2>)
              9 MAKE_CLOSURE             0
             12 STORE_FAST               2 (p)

  3          15 BUILD_LIST               0
             18 LOAD_FAST                0 (xs)
             21 GET_ITER            
        >>   22 FOR_ITER                24 (to 49)
             25 STORE_FAST               3 (x)
             28 LOAD_FAST                2 (p)
             31 LOAD_FAST                3 (x)
             34 CALL_FUNCTION            1
             37 POP_JUMP_IF_FALSE       22
             40 LOAD_FAST                3 (x)
             43 LIST_APPEND              2
             46 JUMP_ABSOLUTE           22
        >>   49 RETURN_VALUE        
>>> dis.dis(method2)
  2           0 BUILD_LIST               0
              3 LOAD_FAST                0 (xs)
              6 GET_ITER            
        >>    7 FOR_ITER                33 (to 43)
             10 STORE_FAST               2 (x)
             13 LOAD_CLOSURE             0 (el)
             16 BUILD_TUPLE              1
             19 LOAD_CONST               1 (<code object <lambda> at 0x101fd37b0, file "<stdin>", line 2>)
             22 MAKE_CLOSURE             0
             25 LOAD_FAST                2 (x)
             28 CALL_FUNCTION            1
             31 POP_JUMP_IF_FALSE        7
             34 LOAD_FAST                2 (x)
             37 LIST_APPEND              2
             40 JUMP_ABSOLUTE            7
        >>   43 RETURN_VALUE        

LOAD_CONST 操作码加载 lambda 体的编译代码; MAKE_CLOSURE 从中创建 lambda。对于 method1 这发生一次,而对于 method2 这在循环的每次迭代中重复(从 FOR_ITER 操作码到 JUMP_ABSOLUTE 操作码);请注意 method1 中变量 pLOAD_FAST 操作码,它引用局部变量。

关于python - Python 列表理解的性能 : rebuilding lambda in `if` part?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14235594/

相关文章:

python - 是否有与 Python 的列表理解等效的 Scala?

python - 同时在python中运行两行代码?

performance - R和Matlab速度之间的差异

performance - 快速硬件整数除法

Python 列表理解性能

python - 从包含多个模式的列表中查找字符串

python - 在python脚本中检测相似文档的算法

Python:从偏移输入中检索 WordNet 上位词

python - 从命令行脚本使用 PyCharm 检查

c# - C# 中未使用的 "using"指令的性能影响