2017-09-06 109 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