我一直被告知python的本机追加是一个缓慢的函数,应该避免在for循环中。然而,经过几次小测试,我发现在使用 for 循环迭代它时,它的性能比 numpy 数组更差:
第一次测试 - 数组/列表构造
Python 原生列表追加
def pythonAppend(n):
x = []
for i in range(n):
x.append(i)
return x
%timeit pythonAppend(1000000)
Numpy 分配数组然后访问
def numpyConstruct(n):
x = np.zeros(n)
for i in range(n):
x[i] = i
return x
%timeit numpyConstruct(1000000)
结果:
蟒蛇时间:179毫秒
时间:189 ms
第二次测试 - 访问元素
n = 1000000
x = pythonAppend(n)
arr = numpyConstruct(n)
order = arr[:]; np.random.shuffle(order); order = list(order.astype(int))
def listAccess(x):
for i in range(len(x)):
x[i] = i/3.
return x
def listAccessOrder(x, order):
for i in order:
x[i] = i/3.
return x
%timeit listAccess(x)
%timeit listAccess(arr)
%timeit listAccessOrder(x, order)
%timeit listAccessOrder(arr, order)
%timeit arr / 3.
结果
Python -顺序:175 ms
数字 - 顺序:198 毫秒
Python -shuffled 访问:2.08 秒
NUMPY 随机访问:2.15 秒
NUMPY 矢量化访问:1.92ms
这些结果对我来说非常令人惊讶,因为我认为至少由于 python 是链表,由于必须遵循一系列指针,访问元素会比 numpy 慢。还是我误解了 python 列表实现?另外,为什么 python 列表的性能略好于 numpy 等价物 - 我想大部分效率低下来自使用 python for 循环,但 python 的附加仍然优于 numpy 访问和分配。
Python 列表不是链表。 列表对象的数据缓冲区包含指向对象的链接/指针(否则在内存中(。 因此,获取ith
元素很容易。 缓冲区还具有增长余量,因此append
插入新元素的链接很简单。 缓冲区必须定期重新分配,但 Python 可以无缝地管理它。
numpy 数组也有一个 1d 数据缓冲区,但它包含数值(更一般地说是dtype
所需的字节(。 获取很容易,但它必须创建一个新的python对象来"包含"值("拆箱"(。赋值还需要从 python 对象转换为将要存储的字节。
通常,我们发现通过附加到列表(最后有一个np.array
调用(来创建新数组与分配给预分配数组相比具有竞争力。
通过 numpy 数组迭代通常比遍历列表慢。
我们强烈建议不要使用np.append
(或某些变体(来迭代增长数组。 每次都会创建一个带有完整副本的新数组。
在编译的代码中完成迭代时,numpy 数组速度很快。 通常,这是使用全数组方法。c
代码可以循环访问数据缓冲区,直接对值进行操作,而不是在每一步调用 python 对象方法。