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

Why is copying from a ndarray to another ndarray memory consuming?

我在尝试使用 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 以及 python 3.6 with numpy 1.13.1 on win7 64bit)时,最后一行代码的执行需要一个大量内存(~ 10 Gb),这对我来说听起来很奇怪。

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

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

来自 'Index arrays' 下的 numpy 文档:

NumPy arrays may be indexed with other arrays (or any other sequence- like object that can be converted to an array, such as lists, with the exception of tuples; see the end of this document for why this is). The use of index arrays ranges from simple, straightforward cases to complex, hard-to-understand cases. For all cases of index arrays, what is returned is a copy of the original data, not a view as one gets for slices.

换句话说,您假设您的行 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 似乎并没有提高性能。

问题不在于复制,问题在于你的数组很大:

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

所以数组几乎有 7GB 的大小。那为什么它只在赋值行触发B[:,:] = A[idx,:]?

那是因为 zeros 直到您想要使用它时才真正分配数组。在将它编入索引(在 A 的情况下:A[idx, :])或分配给它(在 B 的情况下:B[:,:] =)之前,您不会使用它。

所以没有什么奇怪的事情发生,这只是 AB.

实际需要的内存量