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



不确定标题是否是正确的术语。

如果你必须比较2个字符串(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 ms

100个环路,3个最佳:每个环路6.9 ms

10000个字符

生成器表达式

列出

10个环路,3个最佳:每个环路87.5毫秒

10个环路,3个最佳:每个环路 76.1 ms

10000个字符

生成器表达式

列出

1个环路,3个最佳:每个环路908毫秒

1个环路,最佳3:每个环路840毫秒

我查看了每个构造的反汇编(使用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)

然后用每个函数调用CCD_ 1。

对于列表理解:

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,这样列表理解在该阈值之前会表现得稍微好一点(因为内存使用不会成为问题(,但在该阈值之后,生成器将显著地表现得更好。

生成器通常比列表理解慢,生成器的全部目的是提高内存效率,因为它们通过以懒惰的方式创建每个项来生成每个项(只有在实际需要时(。他们喜欢记忆效率而不是速度。

最新更新