为什么 a.insert(0,0) 比 a[0:0]=[0] 慢得多?

Kel*_*ndy 70 python performance

使用列表的insert函数比使用切片分配实现相同的效果要慢得多:

> python -m timeit -n 100000 -s "a=[]" "a.insert(0,0)"
100000 loops, best of 5: 19.2 usec per loop

> python -m timeit -n 100000 -s "a=[]" "a[0:0]=[0]"
100000 loops, best of 5: 6.78 usec per loop
Run Code Online (Sandbox Code Playgroud)

(请注意,这a=[]只是设置,因此a开始时为空,但后来增长到 100,000 个元素。)

起初我想可能是属性查找或函数调用开销左右,但在末尾插入表明这是可以忽略不计的:

> python -m timeit -n 100000 -s "a=[]" "a.insert(-1,0)"
100000 loops, best of 5: 79.1 nsec per loop
Run Code Online (Sandbox Code Playgroud)

为什么可能更简单的专用“插入单个元素”功能这么慢?

我也可以在 repl.it 上重现它:

from timeit import repeat

for _ in range(3):
  for stmt in 'a.insert(0,0)', 'a[0:0]=[0]', 'a.insert(-1,0)':
    t = min(repeat(stmt, 'a=[]', number=10**5))
    print('%.6f' % t, stmt)
  print()

# Example output:
#
# 4.803514 a.insert(0,0)
# 1.807832 a[0:0]=[0]
# 0.012533 a.insert(-1,0)
#
# 4.967313 a.insert(0,0)
# 1.821665 a[0:0]=[0]
# 0.012738 a.insert(-1,0)
#
# 5.694100 a.insert(0,0)
# 1.899940 a[0:0]=[0]
# 0.012664 a.insert(-1,0)
Run Code Online (Sandbox Code Playgroud)

我在 Windows 10 64 位上使用 Python 3.8.1 32 位。
repl.it 在 Linux 64 位上使用 Python 3.8.1 64 位。

use*_*ica 65

我认为这可能只是他们忘记使用memmovein list.insert。如果您查看用于移动元素的代码 list.insert,您会发现它只是一个手动循环:

for (i = n; --i >= where; )
    items[i+1] = items[i];
Run Code Online (Sandbox Code Playgroud)

list.__setitem__在切片分配路径上使用memmove

memmove(&item[ihigh+d], &item[ihigh],
    (k - ihigh)*sizeof(PyObject *));
Run Code Online (Sandbox Code Playgroud)

memmove 通常会进行很多优化,例如利用 SSE/AVX 指令。

  • 谢谢。创建了一个引用此问题的[问题](https://bugs.python.org/issue39801)。 (8认同)
  • 如果解释器是在启用“-O3”自动矢量化的情况下构建的,则该手动循环可能会有效编译。但是,除非编译器将循环识别为 memmove 并将其编译为对“memmove”的实际调用,否则它只能利用编译时启用的指令集扩展。(如果您使用“-march=native”构建自己的文件,那很好,而对于使用基线构建的发行版二进制文件则不然)。默认情况下,GCC 不会展开循环,除非您使用 PGO (`-fprofile-generate` / run / `...-use`) (7认同)