Python 为什么求和列表理解比生成器表达式快?
不确定标题是否是正确的术语 如果必须比较两个字符串(A、B)中的字符,并根据A计算B中字符的匹配数:Python 为什么求和列表理解比生成器表达式快?,python,python-3.x,Python,Python 3.x,不确定标题是否是正确的术语 如果必须比较两个字符串(A、B)中的字符,并根据A计算B中字符的匹配数: sum([ch in A代表ch in B]) 在%timeit上比 sum(A中的ch表示B中的ch) 我知道第一个将创建一个bool列表,然后对值1求和。 第二个是发电机。我不清楚它在内部做什么,为什么它会慢一些 谢谢 使用%timeit结果进行编辑: 10个字符 生成器表达式 名单 10000个回路,最好为3:112µs/回路 10000个回路,最佳3个:每个回路94.6µs 1000
sum([ch in A代表ch in B])
在%timeit上比
sum(A中的ch表示B中的ch)
我知道第一个将创建一个bool列表,然后对值1求和。
第二个是发电机。我不清楚它在内部做什么,为什么它会慢一些
谢谢
使用%timeit结果进行编辑:
10个字符
生成器表达式
名单
10000个回路,最好为3:112µs/回路
10000个回路,最佳3个:每个回路94.6µs
1000个字符
生成器表达式
名单
100个回路,最好为3:8.5 ms/回路
100个回路,最好为3:6.9毫秒/回路
10000个字符
生成器表达式
名单
10个回路,最佳3个:每个回路87.5毫秒
10个环路,最佳3个:每个环路76.1毫秒
100000个字符
生成器表达式
名单
1个循环,最佳3:908毫秒/循环
1个循环,最好是每个循环3:840毫秒生成器通常比列表理解慢,生成器的整个要点是提高内存效率,因为它们以惰性方式创建每个项目(仅在实际需要时)来生成每个项目。他们更喜欢内存效率而不是速度。我查看了每个构造的分解(使用)。为此,我声明了以下两个函数:
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
0加载\u全局0(总和)
2加载常数1()
4加载常数2(“生成表达式…”)
6生成函数0
8负载_全局1(B)
10获得
12调用函数1
14调用函数1
16返回值
但是这两个示例之间的sum
反汇编是恒定的,唯一的区别是加载generation\u expression..
vslist\u comprehension..
(因此只需加载不同的局部变量)
前两个分解之间的不同字节码指令是列表理解的LIST\u APPEND
,而生成器表达式的YIELD\u VALUE
和POP\u TOP
的结合
我不会假装我知道Python字节码的本质,但我从中得到的是生成器表达式被实现为一个队列,在队列中生成值,然后弹出。这种弹出不一定要发生在列表理解中,这让我相信在使用生成器时会有少量开销
这并不意味着发电机总是会变慢。生成器在内存效率方面表现出色,因此会有一个阈值N,这样列表理解在该阈值之前的性能会稍好一些(因为内存使用不会有问题),但在该阈值之后,生成器的性能会明显更好。字符串有多长?生成器是数据惰性的,这可能会增加一些开销,短字符串的开销可能会大于不复制数据保存的开销。因为python非常非常擅长创建列表。嗯,即使字符串长度为10000000,列表版本也比生成器版本快。。。我对速度差也很好奇,它们似乎在逐渐收敛。最终,必须进行两次遍历并构建列表的成本可能会超过更昂贵的生成器遍历。sum(列表理解)首先生成bool列表,然后对1进行求和。生成器表达式有什么不同?在我对生成器的理解中,它们需要多个下一个调用,这可能(但不总是)使它们变慢,请看这里:生成器使用下一个调用,正如您在这里所看到的:因此,字符串等较长的可重用项会导致更多的下一个调用,这会减慢速度。我找到了一个So答案,详细解释了这一点:您能否显示用于获取字节码指令的代码?我也试过了,但得到了不同的指示。@jakub Yup,把它添加到了答案中。谢谢,我现在得到了相同的结果。很好的回答同样,
sum
是用C编写的,它使用列表迭代器的tp\u next
函数,不必为每个值遍历yield/pop运算码。我认为额外的开销是在收益率上。生成器是否会产生一些然后被求和的东西,就像列表理解生成布尔列表一样?