Python 为什么从一个数据阵列复制到另一个数据阵列会消耗内存?

Python 为什么从一个数据阵列复制到另一个数据阵列会消耗内存?,python,numpy,memory,reorderlist,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,:] #

我在尝试用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 64位上运行带有numpy 1.13.1的python 3.6),最后一行代码的执行需要大量内存(~10 Gb),这听起来很奇怪

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


我想我做错了什么,但没有发现什么。。。也许有人能帮我?

来自“索引数组”下的
numpy
文档:

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

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

我无法理解为什么内存使用会发生如此大的变化。但是,看看您原来的数组形状,
s=(300003000)
,如果我没有计算错的话,对于64位的数字,这大约相当于6.7GB。因此,创建额外的阵列时,额外的内存使用似乎是合理的

编辑

针对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[:,:]=
)之前,您不会使用它


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

实际需要的内存量,你知道
np.random.shuffle
返回
None
,所以你只需使用
A[None,:]
?@MSeifert甚至将相关行更正为
np.random.shuffle(idx)
产生所描述的行为。对我来说,在Mac上,最后一行将峰值内存使用量从大约24 MB增加到大约3.5 GB(使用
resource.getrusage
)进行测试)。是的,但是讨论一些可能无法达到预期目的的内容是没有用的。是的,你是对的。。。很抱歉犯了这个错误,但正如@Thomas所指出的,在删除作业时问题仍然存在。你比我抢先一步:t谢谢。。。那么,如何实际预分配内存呢?谢谢你的帮助@您可以使用davidguez立即分配内存。:)您还可以使用A.take(idx,out=B)。这可以通过直接写入
B
@davidguez来避免临时拷贝—没问题。如果答案解决了你的问题,别忘了接受它:)@MSeifert哦,MB是个打字错误。你是对的,显然我不能再正确地计算64/8了——我会修正它……好的,但3.4可能是10GB的完美解释:3.4表示
a
,3.4表示
B
,3.4表示临时数组
a[idx,:]
=10.2GB(匹配!):)@没错。可能这取决于
np.zeros
用于创建原始数组的默认类型。我的假设是64位浮点数,但可能只是32位整数——这就加起来了。为了共享计算,首先我使用了
300000*3000*4/1024**3=3.35
,然后是
300000*3000*8/1024**3=6.71
。的默认数据类型是
float
,即
np.float64
(8字节)。也许真正的剧本不是张贴在这里的谢谢你。。。我理解,但是,有没有一种方法可以在不创建临时对象的情况下执行此复制