当我阅读和搜索究竟是什么导致循环和列表理解之间的性能差异时(基于下面的简单测试用例,列表理解更快),我在 SO 中遇到了以下帖子:
简单的测试用例:
def f1(): t = [] for i in range(10000): t.append(i) def f2(): t = [i for i in range(10000)]
我从上面的帖子中了解到,主要区别如下;
然后我使用反汇编程序查看详细信息,我看到了以下代码块的以下步骤:
代码:
def f2(): t = [i for i in range(10000)] dis.dis(f2)
反汇编结果:
0 BUILD_LIST 2 LOAD_FAST 4 FOR_ITER 6 STORE_FAST 8 LOAD_FAST 10 LIST_APPEND 12 JUMP_ABSOLUTE 14 RETURN_VALUE
基于 Python文档;0 BUILD_LIST创建列表并10 LIST_APPEND使用 append 方法对比上面的相关帖子:
0 BUILD_LIST
10 LIST_APPEND
LIST_APPEND(i) Calls list.append(TOS[-i], TOS). Used to implement list comprehensions. BUILD_LIST(count) Works as BUILD_TUPLE, but creates a list.
我不知道我在这里错过了什么。列表理解构建和附加的方式是否不同于 for 循环,因为无论如何都LIST_APPEND包含append方法并BUILD_LIST创建一个列表?或者性能差异的原因可能是其他原因?有人可以为我澄清一下吗?
LIST_APPEND
append
BUILD_LIST
我尝试了一种不同的方法:
from collections import Counter def f1(): t = [] for i in range(10000): t.append(i) def f2(): t = [i for i in range(10000)] f1i = Counter(i.opname for i in dis.get_instructions(f1)) f2i = Counter(i.opname for i in dis.get_instructions(f2)) print(f"Only in regular append: {f1i - f2i}") print(f"Only in list comprehension: {f2i - f1i}")
结果是(Python 3.7.6):
Only in regular append: Counter({'LOAD_FAST': 2, 'BUILD_LIST': 1, 'STORE_FAST': 1, 'SETUP_LOOP': 1, 'FOR_ITER': 1, 'LOAD_METHOD': 1, 'CALL_METHOD': 1, 'POP_TOP': 1, 'JUMP_ABSOLUTE': 1, 'POP_BLOCK': 1}) Only in list comprehension: Counter({'LOAD_CONST': 2, 'MAKE_FUNCTION': 1, 'CALL_FUNCTION': 1})
您可以看到“常规”追加使用LOAD_METHOD(for list.append)、,LOAD_FAST和CALL_METHOD每次POP_TOP迭代:
LOAD_METHOD
list.append
LOAD_FAST
CALL_METHOD
POP_TOP
dis.dis(f1) 5 0 BUILD_LIST 0 2 STORE_FAST 0 (t) 6 4 SETUP_LOOP 26 (to 32) 6 LOAD_GLOBAL 0 (range) 8 LOAD_CONST 1 (10000) 10 CALL_FUNCTION 1 12 GET_ITER >> 14 FOR_ITER 14 (to 30) 16 STORE_FAST 1 (i) 7 18 LOAD_FAST 0 (t) 20 LOAD_METHOD 1 (append) 22 LOAD_FAST 1 (i) 24 CALL_METHOD 1 26 POP_TOP 28 JUMP_ABSOLUTE 14 >> 30 POP_BLOCK >> 32 LOAD_CONST 0 (None) 34 RETURN_VALUE
还建议记住,操作码会从一个版本更改为另一个版本。