Python 为什么切片分配比'list.insert'快?
受到 这里有一个基准:Python 为什么切片分配比'list.insert'快?,python,performance,optimization,python-internals,Python,Performance,Optimization,Python Internals,受到 这里有一个基准: import timeit def test1(): a = [1,2,3] a.insert(0,1) def test2(): a = [1,2,3] a[0:0]=[1] print (timeit.timeit('test1()','from __main__ import test1')) print (timeit.timeit('test2()','from __main__ import test2')) 对我来说,
import timeit
def test1():
a = [1,2,3]
a.insert(0,1)
def test2():
a = [1,2,3]
a[0:0]=[1]
print (timeit.timeit('test1()','from __main__ import test1'))
print (timeit.timeit('test2()','from __main__ import test2'))
对我来说,test2
稍微快一些(~10%)。为什么会这样?我预计会更慢,因为:
(在OS-X 10.5.8上使用python 2.7)您的第一个测试用例必须调用列表
a
上的方法insert
,而test2
中的所有操作都直接以字节码处理。注意下面的test1
反汇编中的CALL\u函数。在Python中,调用函数的代价是适中的:当然,这一代价足以解释运行时的几个百分点差异
>>> import dis
>>> dis.dis(test1)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 BUILD_LIST 3
12 STORE_FAST 0 (a)
3 15 LOAD_FAST 0 (a)
18 LOAD_ATTR 0 (insert)
21 LOAD_CONST 4 (0)
24 LOAD_CONST 1 (1)
27 CALL_FUNCTION 2
30 POP_TOP
31 LOAD_CONST 0 (None)
34 RETURN_VALUE
>>> dis.dis(test2)
2 0 LOAD_CONST 1 (1)
3 LOAD_CONST 2 (2)
6 LOAD_CONST 3 (3)
9 BUILD_LIST 3
12 STORE_FAST 0 (a)
3 15 LOAD_CONST 1 (1)
18 BUILD_LIST 1
21 LOAD_FAST 0 (a)
24 LOAD_CONST 4 (0)
27 LOAD_CONST 4 (0)
30 STORE_SLICE+3
31 LOAD_CONST 0 (None)
34 RETURN_VALUE
错误的解释
我先发布了这篇文章,但经过考虑,我认为这是不正确的。我在这里描述的差异只会在有大量数据需要移动时产生显著的差异,而在这里的测试中不是这样。即使有很多数据,差异也只有几个百分点:
import timeit
def test1():
a = range(10000000)
a.insert(1,1)
def test2():
a = range(10000000)
a[1:1]=[1]
>>> timeit.timeit(test1, number=10)
6.008707046508789
>>> timeit.timeit(test2, number=10)
5.861173868179321
方法list.insert
由listobject.c
中的函数实现。您将看到,它逐个复制列表尾部的项引用:
for (i = n; --i >= where; )
items[i+1] = items[i];
另一方面,片分配由函数实现,该函数调用memmove
:
memmove(&item[ihigh+d], &item[ihigh],
(k - ihigh)*sizeof(PyObject *));
所以我认为你的问题的答案是C库函数memmove
比简单循环优化得更好。请看:我相信,当从list\u ass\u slice
调用时,它最终会调用您所看到的经过大量手动优化的对象。好问题:)我自己对此很好奇。@TimPietzcker——您的基准测试真的让我感到很困惑。我必须自己测试:)。我猜insert
是通过调用片分配代码来实现的。@KeithRandall:不,这两个代码路径是分开的(list\u assu\u slice
vs.ins1
in)。@JoranBeasley——在我的基准测试中,列表没有增长。但在Tim Pietzcker的(见链接答案)中,它在基准测试中增长。(我想长度大约为100000)我的直觉是,test1
速度较慢,因为每个a.insert
都有属性查找和绑定方法创建。为了测试这一点,我将test1
定义为deftest1(ins=list.insert)
,并将a.insert(0,1)
替换为ins(a,0,1)
,几乎没有可测量的差异我不相信你的新加入。。。a[0:0]
是否隐式地需要调用\uuuu setitem\uuuu
(通过STORE\u SLICE
),因为python无法知道a
是列表而不是其他类型?(如果我错了,请纠正我……我不是阅读dis.dis
输出的专家)。调用\uuuu setitem\uuuu
与调用插入
有什么不同?如果有什么需要的话,setitem
需要构造一个额外的切片
对象,然后对其进行解释…@user4815162342——它不也需要隐式地对a[0:0]
进行属性查找吗?(它是如何访问\uuuuuSetItem\uuuuuuuuuuuu
的?)它没有-访问\uuuuuuuuuSetItem\uuuuuuuuu
(以及许多其他特殊方法)是实现类型对象的C结构。它是通过简单的指针解引用来访问的。