n项列表的不同追加和python中的[0] * n?

时间:2013-12-21 18:57:35

标签: python

我有两个相同的代码 Code1

>>> a=[0]*60000000

Code2

>>> a=[]
>>> for i in range(0,60000000):
        a.append(0)

在我的电脑中Code1所需时间为1秒,但Code2为480秒!! 为什么?哇是不同的?

3 个答案:

答案 0 :(得分:3)

(接下来我假设你使用的是Python 3;在Python 2中,故事情节类似,但我们必须讨论由range引起的内存分配成本。)

你的第二个程序花费时间在Python字节码中运行。让我们使用dis模块对其进行反汇编:

>>> import dis
>>> dis.dis('a=[]\nfor i in range(0,60000000):\n a.append(0)')
  1           0 BUILD_LIST               0 
              3 STORE_NAME               0 (a) 

  2           6 SETUP_LOOP              36 (to 45) 
              9 LOAD_NAME                1 (range) 
             12 LOAD_CONST               0 (0) 
             15 LOAD_CONST               1 (60000000) 
             18 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
             21 GET_ITER             
        >>   22 FOR_ITER                19 (to 44) 
             25 STORE_NAME               2 (i) 

  3          28 LOAD_NAME                0 (a) 
             31 LOAD_ATTR                3 (append) 
             34 LOAD_CONST               0 (0) 
             37 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
             40 POP_TOP              
             41 JUMP_ABSOLUTE           22 
        >>   44 POP_BLOCK            
        >>   45 LOAD_CONST               2 (None) 
             48 RETURN_VALUE         

循环从字节22到41运行,每次循环循环,Python必须解码并执行七字节代码指令,并调用函数(函数为a.append)。这是4.2亿字节的代码指令和6000万个函数调用。

而您的第一个程序花费时间在本机代码中运行

>>> dis.dis('a = [0] * 60000000')
  1           0 LOAD_CONST               0 (0) 
              3 BUILD_LIST               1 
              6 LOAD_CONST               1 (60000000) 
              9 BINARY_MULTIPLY      
             10 STORE_NAME               0 (a) 
             13 LOAD_CONST               2 (None) 
             16 RETURN_VALUE         

你可以看到,不仅没有循环,也没有函数调用。所有工作都在BINARY_MULTIPLY指令内“发生”,发送到list_multiply in listobject.c,由于列表[0]只包含一个元素,因此结果将在此紧密循环中构建第529-536行:

if (Py_SIZE(a) == 1) {
    elem = a->ob_item[0];
    for (i = 0; i < n; i++) {
        items[i] = elem;
        Py_INCREF(elem);
    }
    return (PyObject *) np;
}

答案 1 :(得分:0)

Python 2.7中的

range自己创建一个列表,而不是迭代器。因此,您将Code2中的工作加倍。这意味着,您首先创建一个6000000项的列表,然后迭代它。 Code1不需要那个中间步骤。

  

此[range]是一个多功能函数,用于创建包含算术的列表   级数

Soure:Range Python 2.x

将其与Python 3.x进行比较:

  

范围实际上是一个不可变的序列,而不是一个函数   类型,如范围和序列类型中记录的 - 列表,元组,范围。

来源:Range Python 3.x

尝试使用xrange代替range,然后再次测试。

答案 2 :(得分:0)

忽略您使用range而不是xrange的事实,有两件事情正在发生:

1)虽然函数到达相同的输出,但函数不会做同样的事情。您的code1表示“[0]连接在一起的6000万份”。这不是Code2正在做的事情,显然 - 它必须将列表的append方法称为6000万次。

2)python中的显式循环很慢。在某个地方Code1有一个循环,但它以“C速度”发生,而不是蟒蛇速度。

def f():
    a = [0]*6000000

def g():
    a = []
    for i in xrange(6000000):
        a.append(0)

%timeit f()
10 loops, best of 3: 31 ms per loop

%timeit g()
1 loops, best of 3: 358 ms per loop

在这样的紧密循环中,大部分开销都在循环本身中,而不是其中包含的操作。因此,优化紧密循环的一种流行方法是将它们理解为:

def h():
    [0 for _ in xrange(6000000)]

%timeit h()
10 loops, best of 3: 151 ms per loop

请注意,这两个函数再次没有做同样的事情,这就是fh之间存在差异的原因。