2017-09-06 111 views
3

当我尝试用numpy混洗多维数组时,我遇到了一个问题。 这个问题可以用下面的代码被复制:为什么要从ndarray复制到另一个ndarray内存?

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 

当运行该代码(Win7上64位蟒2.7以及3.6蟒与numpy的1.13.1),代码的最后一行的执行需要大量的记忆(〜10 Gb),这听起来很奇怪。

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

我想我做错了什么,但没有找到...可能有人可以帮助我?

+1

你知道'np.random.shuffle'返回'None',所以你只需用'A [None,:]'索引? – MSeifert

+0

@MSeifert甚至将有问题的行更正为'np.random.shuffle(idx)'产生所描述的行为。对于Mac而言,最后一行将峰值内存使用量从约24 MB增加到约3.5 GB(使用'resource.getrusage'测试它)。 –

+0

是的,但讨论一些可能无法做到的事情是没有用的。 – MSeifert

回答

2

从下“索引阵列”的numpy文档:

NumPy的阵列可以与其它阵列(或任何其他序列 - 像对象,可以被转换成一个阵列,如列表进行索引,以元组的 异常;请参阅本文末尾的原因)。 索引数组的使用范围从简单明了的案例到 复杂而难以理解的案例。 对于索引数组的所有情况,返回 是原始数据的副本,而不是针对 切片获得的视图。

换句话说,你的假设,你的行B[:,:] = A[idx,:](后修正一行@MSeifert指出)仅诱导从A元素B的复制是不正确的。相反,numpy首先从索引A创建一个新阵列,然后将其元素复制到B中。

为什么内存使用量变化如此之大超出了我的想象。然而,看看你的原始数组形状s=(300000,3000),如果我没有计算错误的话,对于64位数字来说,这大概为6.7GB。因此创建额外的数组,额外的内存使用似乎似乎合理。

编辑

反应到OP的评论,我做了关于不同的方式的A洗牌后的行分配给B表现了一些测试。首先,在这里一个小测试B=A[idx,:]确实创建一个新的ndarrayA不仅仅是一个观点:

>>> 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 -loop不会执行得太差,特别是如果您只想分配部分行而不是整个数组。令人惊讶的是numba似乎没有提升性能。

+0

@ MSeifert哦,MB是一个错字。你说得对,显然我无法正确计算64/8 - 我会修正它... –

+0

好的,但3.4对于10GB来说是一个完美的解释:3.4为'A',3.4为对于临时数组'A [idx,:]'= 10.2 GB(匹配!)为'B'和3.4。 :) – MSeifert

+0

@MSeifert这是正确的。也许它取决于'np.zeros'用来创建原始数组的默认类型。我的假设是64位浮点数,但也许只有32位整数 - 这会加起来。为了分享计算,首先我有'300000 * 3000 * 4/1024 ** 3 = 3.35',然后'300000 * 3000 * 8/1024 ** 3 = 6.71'。 –

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实际需要的内存量。

+0

你打我吧:D –

+0

谢谢......但是如何实际预先分配内存呢?感谢您的帮助! –

+0

@davidguez你可以使用立即分配内存的['np.full(shape,0)'](https://docs.scipy.org/doc/numpy/reference/generated/numpy.full.html)。 :) – MSeifert