小能豆

“创建列表”和“附加元素”与简单循环有何不同?

python

当我阅读和搜索究竟是什么导致循环和列表理解之间的性能差异时(基于下面的简单测试用例,列表理解更快),我在 SO 中遇到了以下帖子:

简单的测试用例:

def f1():
    t = []
    for i in range(10000):
        t.append(i)

def f2():
    t = [i for i in range(10000)]

我从上面的帖子中了解到,主要区别如下;

  1. For 循环构建一个列表,但列表推导式不
  2. For loop loads append method on each iteration 但是,list comprehension does

然后我使用反汇编程序查看详细信息,我看到了以下代码块的以下步骤:

代码:

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 方法对比上面的相关帖子:

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创建一个列表?或者性能差异的原因可能是其他原因?有人可以为我澄清一下吗?


阅读 108

收藏
2023-06-02

共1个答案

小能豆

我尝试了一种不同的方法:

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_FASTCALL_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

还建议记住,操作码会从一个版本更改为另一个版本。

2023-06-02