不确定标题是否正确。
如果您必须比较两个字符串(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毫秒
答案 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_VALUE
和POP_TOP
的合取。
我不会假装我知道Python字节码的内在特性,但是我从中收集的是生成器表达式被实现为一个队列,在该队列中生成值,然后将其弹出。弹出列表并不一定要发生这种情况,这使我相信使用生成器会产生少量开销。
现在,这并不意味着发电机总是会变慢。生成器擅长提高内存效率,因此会有一个阈值N,使得列表理解在此阈值之前会稍好一些(因为使用内存不会有问题),但是在此阈值之后,生成器将明显地< / em>表现更好。
答案 1 :(得分:1)
生成器通常比列表理解要慢,生成器的全部要点是提高内存效率,因为它们通过以惰性方式创建它们(仅在实际需要时)来生成每个项。他们主张记忆效率胜于速度。