为什么从一个 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[:,:] =
)之前,您不会使用它。
所以没有什么奇怪的事情发生,这只是 A
和 B
.
实际需要的内存量
我在尝试使用 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[:,:] =
)之前,您不会使用它。
所以没有什么奇怪的事情发生,这只是 A
和 B
.