不确定标题是否是正确的术语。
如果你必须比较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_VALUE
和POP_TOP
的结合。
我不会假装我知道Python字节码的本质,但我从中了解到,生成器表达式被实现为一个队列,在那里生成值,然后弹出。在列表理解中,这种弹出不一定会发生,这让我相信在使用生成器时会有少量开销。
现在,这并不意味着发电机总是会变慢。生成器擅长提高内存效率,因此会有一个阈值N,这样列表理解在该阈值之前会表现得稍微好一点(因为内存使用不会成为问题(,但在该阈值之后,生成器将显著地表现得更好。
生成器通常比列表理解慢,生成器的全部目的是提高内存效率,因为它们通过以懒惰的方式创建每个项来生成每个项(只有在实际需要时(。他们喜欢记忆效率而不是速度。