为什么从ndarray复制到另一个ndarray内存消耗?

时间:2017-09-06 11:48:12

标签: python numpy memory reorderlist

我试图用numpy改组多维数组时遇到问题。 可以使用以下代码重现该问题:

import numpy as np
s=(300000, 3000)
n=s[0]
print ("Allocate")
A=np.zeros(s)
B=np.zeros(s)
print ("Index")
idx = np.arange(n)
print ("Shuffle")
idx = np.random.shuffle(idx)
print ("Arrange")
B[:,:] = A[idx,:] # THIS REQUIRES A LARGE AMOUNT OF MEMORY

当运行此代码(python 2.7以及win7 64bit上的numpy 1.13.1的python 3.6)时,最后一行代码的执行需要大量内存(~10 Gb),这听起来很奇怪我

实际上,我希望将数据从一个数组复制到另一个数组,这两个数据都是预先分配的,所以我可以理解副本会消耗时间,但不明白它为什么需要内存。

我想我做错了但却找不到......也许有人可以帮助我?

2 个答案:

答案 0 :(得分:3)

问题不在于复制问题是你的阵列是巨大的:

>>> 300000 * 3000 * 8 / 1024 / 1024 / 1024  # 8 byte floats, 300000 * 3000 elements converted to GB
6.705522537231445

因此这些阵列几乎是7GB。那么为什么它只在分配线B[:,:] = A[idx,:]触发?

那是因为zeros实际上没有分配数组,直到您想要使用它。并且在您将其编入索引之前不会使用它(如果是AA[idx, :])或分配给它(如果是BB[:,:] =)。

所以没有什么奇怪的事情发生,它只是AB实际需要的内存量。

答案 1 :(得分:2)

来自'索引数组':

下的numpy文档
  

NumPy数组可以用其他数组(或任何其他序列)索引 -   喜欢可以转换为数组的对象,例如列表   元组除外;请参阅本文档的最后部分,了解其原因。   索引数组的使用范围从简单,直接的情况到   复杂,难以理解的案例。 对于索引数组的所有情况,什么   返回的是原始数据的副本,而不是获取的视图   切片。

换句话说,您假设您的行B[:,:] = A[idx,:](在更正@MSeifert指出的行之后)仅导致将元素从A复制到B是不正确的。相反,numpy首先从索引A创建一个新数组,然后将其元素复制到B

为什么内存使用量的变化超出我的范围。但是,查看原始数组形状s=(300000,3000),对于64位数字,如果我没有计算错误,则大约为6.7 GB。因此创建额外的数组,实际上额外的内存使用似乎是合理的。

修改

对OP的评论作出反应,我做了一些关于将混合行A分配给B的不同方法的表现的测试。首先,这是B=A[idx,:]确实创建新ndarray的小型测试,而不只是A的视图:

>>> import numpy as np
>>> a = np.arange(9).reshape(3,3)
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> b = a[[2,0,1],:]
>>> b
array([[6, 7, 8],
       [0, 1, 2],
       [3, 4, 5]])
>>> b[0]=-5
>>> b
array([[-5, -5, -5],
       [ 0,  1,  2],
       [ 3,  4,  5]])
>>> a
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

确实,为b分配新值会使a保持不变。然后我做了一些时间测试,关于以A行进行混洗并将它们放入B的最快方法:

import numpy as np
import timeit
import numba as nb

s=(300000, 3000)
A = np.arange(s[0]*s[1]).reshape(s)
idx = np.arange(s[0])

#directly keep the indexed array
def test1(x,idx):  
    return x[idx,:]

#the method of the OP
def test2(x, y, idx):
    y[:,:]=x[idx,:]
    return y

#using a simple for loop, e.g. if only part of the rows should be assigned
def test3(x,y,idx):
    for i in range(len(idx)):
        y[i,:] = x[idx[i],:]
    return y

#like test3, but numba-compiled
@nb.jit(nopython=True)
def test4(x,y,idx):
    for i in range(len(idx)):
        y[i,:] = x[idx[i],:]
    return y

B = np.zeros(s)
res = timeit.Timer(
    'test1(A,idx)',
    setup = 'from __main__ import test1, A, idx'
    ).repeat(7,1)

print('test 1:', np.min(res), np.max(res), np.mean(res))

B = np.zeros(s)
res = timeit.Timer(
    'test2(A,B,idx)',
    setup = 'from __main__ import test2, A, B, idx'
    ).repeat(7,1)

print('test 2:', np.min(res), np.max(res), np.mean(res))


B = np.zeros(s)
res = timeit.Timer(
    'test3(A,B,idx)',
    setup = 'from __main__ import test3, A, B, idx'
    ).repeat(7,1)

print('test 3:', np.min(res), np.max(res), np.mean(res))


B = np.zeros(s)
res = timeit.Timer(
    'test4(A,B,idx)',
    setup = 'from __main__ import test4, A, B, idx'
    ).repeat(7,1)

print('test 4:', np.min(res), np.max(res), np.mean(res))

7次运行的结果(最小值,最大值,平均值)为:

test 1: 19.880664938 21.354912988 20.2604536371
test 2: 73.419507756 139.534279557 122.949712777
test 3: 40.030043285 78.001182537 64.7852914216
test 4: 40.001512514 73.397133578 62.0058947516

最后,一个简单的for - 循环执行得不是很糟糕,特别是如果你只想分配部分行而不是整个数组。令人惊讶的是numba似乎没有提高性能。