预分配NumPy数组的最佳方法是什么?

33 投票
3 回答
37179 浏览
提问于 2025-04-16 02:51

我刚接触NumPy和SciPy。从文档上看,提前分配一个数组似乎比后面再用追加、插入或连接的方式来添加数据要高效得多。

比如说,如果我想给一个数组添加一列全是1的数字,我觉得这样做:

ar0 = np.linspace(10, 20, 16).reshape(4, 4)
ar0[:,-1] = np.ones_like(ar0[:,0])

会比这样做更好:

ar0 = np.linspace(10, 20, 12).reshape(4, 3)
ar0 = np.insert(ar0, ar0.shape[1], np.ones_like(ar0[:,0]), axis=1)

我第一个问题是,这样做是否正确(也就是说,第一种方式更好),第二个问题是,目前我只是像这样提前分配我的数组(我在SciPy网站的一些食谱示例中看到过):

np.zeros((8,5))

那么,'NumPy推荐'的做法是什么呢?

3 个回答

2

根据我的经验,numpy.empty() 是预先分配超大数组最快的方法。我说的这些数组的形状大概是 (80,80,300000),数据类型是 uint8

下面是代码:

%timeit  np.empty((80,80,300000),dtype='uint8')
%timeit  np.zeros((80,80,300000),dtype='uint8')
%timeit  np.ones((80,80,300000),dtype='uint8')

还有时间测试的结果:

10000 loops, best of 3: 83.7 µs per loop  # Much faster
1 loop, best of 3: 273 ms per loop
1 loop, best of 3: 272 ms per loop
10

在性能很重要的情况下,np.emptynp.zeros 是初始化numpy数组最快的方法。

下面是每种方法的测试结果,还有其他几种方法。结果以秒为单位。

>>> timeit("np.empty(1000000)",number=1000, globals=globals())
0.033749611208094166
>>> timeit("np.zeros(1000000)",number=1000, globals=globals())
0.03421245135849915
>>> timeit("np.arange(0,1000000,1)",number=1000, globals=globals())
1.2212416112155324
>>> timeit("np.ones(1000000)",number=1000, globals=globals())
2.2877375495381145
>>> timeit("np.linspace(0,1000000,1000000)",number=1000, globals=globals())
3.0824269766860652
28

预分配是一次性分配你所需的所有内存,而调整数组大小(通过调用添加、插入、连接或调整大小等方法)可能需要将数组复制到一个更大的内存块中。所以你说得对,预分配比调整大小更好,而且应该更快。

根据你想创建的内容,有几种“推荐”的方法来预分配numpy数组。比如有 np.zerosnp.onesnp.emptynp.zeros_likenp.ones_likenp.empty_like,还有很多其他可以创建有用数组的方法,比如 np.linspacenp.arange

所以

ar0 = np.linspace(10, 20, 16).reshape(4, 4)

如果这最接近你想要的 ar0,那就很好。

不过,要把最后一列都变成1,我觉得更好的方法是直接说

ar0[:,-1]=1

由于 ar0[:,-1] 的形状是 (4,),所以1会被广播以匹配这个形状。

撰写回答