索引 numpy 数组和 python append 一样慢



我一直被告知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 对象方法。

最新更新