Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/typo3/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python列表前置时间复杂性_Python_Time Complexity - Fatal编程技术网

Python列表前置时间复杂性

Python列表前置时间复杂性,python,time-complexity,Python,Time Complexity,为什么这是代码 res = [] for i in range(10000): res[0:0] = [i] 大约比这个代码快十倍 res = [] for i in range(10000): res = [i] + res 我预计两者都必须移动所有现有的列表元素,以将新的整数置于零索引。随着范围的改变,这两种方法看起来确实都是O(n^2),但是切片分配要比加法快得多,这意味着后者的基本操作大约是加法的10倍 (是的,这两种方法都无法实现此结果,最好使用deque或appe

为什么这是代码

res = []
for i in range(10000):
    res[0:0] = [i]
大约比这个代码快十倍

res = []
for i in range(10000):
    res = [i] + res
我预计两者都必须移动所有现有的列表元素,以将新的整数置于零索引。随着范围的改变,这两种方法看起来确实都是O(n^2),但是切片分配要比加法快得多,这意味着后者的基本操作大约是加法的10倍


(是的,这两种方法都无法实现此结果,最好使用
deque
append
然后反转结果)

在每个示例的相关行上运行反汇编,我们得到以下字节码:

res[0:0]=[i]

  4          25 LOAD_FAST                1 (i)
             28 BUILD_LIST               1
             31 LOAD_FAST                0 (res)
             34 LOAD_CONST               2 (0)
             37 LOAD_CONST               2 (0)
             40 BUILD_SLICE              2
             43 STORE_SUBSCR
  4          25 LOAD_FAST                1 (i)
             28 BUILD_LIST               1
             31 LOAD_FAST                0 (res)
             34 LOAD_GLOBAL              1 (s)
             37 STORE_SUBSCR
res=[i]+res

  4          25 LOAD_FAST                1 (i)
             28 BUILD_LIST               1
             31 LOAD_FAST                0 (res)
             34 BINARY_ADD
             35 STORE_FAST               0 (res)
在第一个示例(切片)中,没有执行
BINARY\u ADD
,只执行了一个存储操作,在添加的情况下,不仅有一个存储操作,还有一个
BINARY\u ADD
操作,它做得更多,这可能是它慢得多的原因。虽然切片表示法确实需要构建切片,但这些操作也非常简单

为了进行更公平的比较,如果切片表示法是预构造和存储的(使用类似
s=slice(0,0)
),我们可以用查找替换切片表示法;生成的字节码如下所示:

res[s]=[i]

  4          25 LOAD_FAST                1 (i)
             28 BUILD_LIST               1
             31 LOAD_FAST                0 (res)
             34 LOAD_CONST               2 (0)
             37 LOAD_CONST               2 (0)
             40 BUILD_SLICE              2
             43 STORE_SUBSCR
  4          25 LOAD_FAST                1 (i)
             28 BUILD_LIST               1
             31 LOAD_FAST                0 (res)
             34 LOAD_GLOBAL              1 (s)
             37 STORE_SUBSCR

这使得它的字节码指令计数相同,现在我们只看到加载和存储指令,而带有
+
操作的指令则需要一条有效的附加指令。

您是对的,在较高的层次上,循环以相同的方式计算基本相同的结果。因此,时间差异是由于使用的Python版本的实现细节造成的。没有语言的属性可以解释这种差异

在python.orgc实现(CPython)中,代码实际上是完全不同的

res[0:0] = [i]
做它看起来像做的;-)
res
的整个内容向右移动一个槽,并将
i
插入在左端创建的孔中。大部分时间都被对平台C库的
memmove()
函数的一次调用所消耗,该函数一次就完成了转换。现代硬件和C库非常擅长快速移动连续的内存片(在C级,Python列表对象就是这样)

在封面下做的更多,主要是由于CPython的参考计数。更像是:

create a brand new list object
stuff `i` into it
for each element of `res`, which is a pointer to an int object:
    copy the pointer into the new list object
    dereference the pointer to load the int object's refcount
    increment the refcount
    store the new refcount back into the int object
bind the name `res` to the new list object
decrement the refcount on the old `res` object
at which point the old res's refcount becomes 0 so it's trash
so for each object in the old res:
    dereference the pointer to load the int object's refcount
    decrement the refcount
    store the new refcount back into the int object
    check to see whether the new refcount is zero
    take the "no, it isn't zero" branch
release the memory for the old list object
更多的原始工作,所有的指针解引用都会跳过内存,这对缓存不友好

实施

res[0:0] = [i]

跳过大部分:它从一开始就知道,仅仅移动
res
内容的位置不能对移动对象的refcount进行任何净更改,因此不必增加或减少这些refcount。C-level
memmove()
几乎就是一团蜡,指向int对象的指针都不需要取消引用。不仅减少了原始工作,而且对缓存非常友好。

FWIW,您还可以执行
res.insert(0,i)
。这应该和切片赋值一样快。谢谢,在某种程度上,我很高兴知道这不是Python语言本身的一个属性,我没有理解错,但这也表明从实现中可以学到很多东西。但不要过度概括;-)你在这里遇到了一个极端的情况。如果你想要一个“一般原则”,在效率很重要的情况下,通常情况下,改变对象比构建新对象运行得更快。即使没有这里的refcount扭曲,您也可以预期在每次迭代中构建和销毁列表的运行速度将明显慢于在整个过程中修改单个列表对象。