为什么求和列表理解要比生成器表达式快?

时间:2020-07-19 01:12:35

标签: python python-3.x

不确定标题是否正确。

如果您必须比较两个字符串(A,B)中的字符并计算B中的字符与A的匹配数:

sum([ch in A for ch in B])

在%timeit上比

更快
sum(ch in A for ch in B)

我知道第一个将创建一个bool列表,然后将值1相加。 第二个是发电机。我不清楚它在内部做什么以及为什么它变慢了?

谢谢。

使用%timeit结果进行编辑:

10个字符

生成器表达式

列表

10000次循环,最佳3:每个循环112 µs

10000次循环,最佳3:每个循环94.6 µs

1000个字符

生成器表达式

列表

100个循环,最佳3:每个循环8.5毫秒

100个循环,最好为3:每个循环6.9毫秒

10,000个字符

生成器表达式

列表

10个循环,每个循环最好3:87.5毫秒

10个循环,每个循环最好3:76.1毫秒

100,000个字符

生成器表达式

列表

1个循环,每个循环最好3:908毫秒

1个循环,最好是3:每个循环840毫秒

2 个答案:

答案 0 :(得分:9)

我研究了每个结构的反汇编(使用dis)。我通过声明这两个函数来做到这一点:

def list_comprehension():
    return sum([ch in A for ch in B])

def generation_expression():
    return sum(ch in A for ch in B)

然后使用每个函数调用dis.dis

对于列表理解:

 0 BUILD_LIST               0
 2 LOAD_FAST                0 (.0)
 4 FOR_ITER                12 (to 18)
 6 STORE_FAST               1 (ch)
 8 LOAD_FAST                1 (ch)
10 LOAD_GLOBAL              0 (A)
12 COMPARE_OP               6 (in)
14 LIST_APPEND              2
16 JUMP_ABSOLUTE            4
18 RETURN_VALUE

以及生成器表达式:

 0 LOAD_FAST                0 (.0)
 2 FOR_ITER                14 (to 18)
 4 STORE_FAST               1 (ch)
 6 LOAD_FAST                1 (ch)
 8 LOAD_GLOBAL              0 (A)
10 COMPARE_OP               6 (in)
12 YIELD_VALUE
14 POP_TOP
16 JUMP_ABSOLUTE            2
18 LOAD_CONST               0 (None)
20 RETURN_VALUE

实际求和的反汇编为:

 0 LOAD_GLOBAL              0 (sum)
 2 LOAD_CONST               1 (<code object <genexpr> at 0x7f49dc395240, file "/home/mishac/dev/python/kintsugi/KintsugiModels/automated_tests/a.py", line 12>)
 4 LOAD_CONST               2 ('generation_expression.<locals>.<genexpr>')
 6 MAKE_FUNCTION            0
 8 LOAD_GLOBAL              1 (B)
10 GET_ITER
12 CALL_FUNCTION            1
14 CALL_FUNCTION            1
16 RETURN_VALUE

但这两个示例之间的sum分解是恒定的,唯一的区别是generation_expression.<locals>.<genexpr>list_comprehension.<locals>.<listcomp>的加载(因此只是加载了一个不同的局部变量)。

对于列表理解,前两个反汇编之间不同的字节码指令是LIST_APPEND,而对于生成器表达式,则是YIELD_VALUEPOP_TOP的合取。

我不会假装我知道Python字节码的内在特性,但是我从中收集的是生成器表达式被实现为一个队列,在该队列中生成值,然后将其弹出。弹出列表并不一定要发生这种情况,这使我相信使用生成器会产生少量开销。

现在,这并不意味着发电机总是会变慢。生成器擅长提高内存效率,因此会有一个阈值N,使得列表理解在此阈值之前会稍好一些(因为使用内存不会有问题),但是在此阈值之后,生成器将明显地< / em>表现更好。

答案 1 :(得分:1)

生成器通常比列表理解要慢,生成器的全部要点是提高内存效率,因为它们通过以惰性方式创建它们(仅在实际需要时)来生成每个项。他们主张记忆效率胜于速度。