【问题标题】:Why is copying from a ndarray to another ndarray memory consuming?为什么从 ndarray 复制到另一个 ndarray 内存消耗?
【发布时间】:2018-02-14 21:32:40
【问题描述】:

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

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

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

【问题讨论】:

  • 您知道np.random.shuffle 返回None,所以您只需使用A[None, :] 进行索引?
  • @MSeifert 甚至将有问题的行更正为 np.random.shuffle(idx) 会产生所描述的行为。对于 Mac 上的我来说,最后一行将峰值内存使用量从大约 24 MB 增加到大约 3.5 GB(使用 resource.getrusage 测试过)。
  • 是的,但讨论一些可能无法达到预期目的的事情是没有用的。
  • 是的,你是对的......对于错误感到抱歉,但正如@Thomas 指出的那样,删除作业时问题仍然存在。

标签: python numpy memory reorderlist


【解决方案1】:

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

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

【讨论】:

  • 你打败了我:D
  • 谢谢...但是如何实际预分配内存?感谢您的帮助!
  • @davidguez 您可以使用立即分配内存的np.full(shape, 0)。 :)
  • 您也可以使用A.take(idx, out=B)。这可以通过直接写入B来避免临时副本。
  • @davidguez 没问题。如果它解决了您的问题,请不要忘记接受答案:)
【解决方案2】:

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

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

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

为什么内存使用变化如此之大,我无法理解。但是,查看您的原始数组形状s=(300000,3000),如果我没有计算错的话,对于 64 位数字,这将达到大约 6.7 GB。因此创建那个额外的数组,额外的内存使用实际上似乎是合理的。

编辑

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

【讨论】:

  • @MSeifert 哦,MB 是错字。你是对的,显然我不能正确计算 64/8 了——我会修复它......
  • 好的,但是 3.4 是 10GB 的完美解释:A 是 3.4,B 是 3.4,临时数组 A[idx, :] 是 3.4 = 10.2 GB(匹配!)。 :)
  • @MSeifert 没错。也许这取决于np.zeros 用于创建原始数组的默认类型。我的假设是 64 位浮点数,但也许它只是 32 位整数——这会加起来。为了分享计算,首先我有300000*3000*4/1024**3=3.35,然后是300000*3000*8/1024**3=6.71
  • zeros 的默认 dtype 是 float,即 np.float64(8 个字节)。也许真正的脚本不是这里发布的。 :D
  • 谢谢...我明白了,但是,有没有办法在不创建临时对象的情况下执行此复制。在类似 C 的代码中,它类似于 for (int i=0, i
猜你喜欢
  • 2021-03-14
  • 2018-04-12
  • 2021-10-15
  • 2018-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多