具有范围或列表的 Numpy repack_fields 分配内存

Numpy repack_fields with range or a list allocates memory

问题:

我正在尝试从大型 numpy 结构化数组中重新打包行和字段的子集。

当我使用切片时,我可以使用 repack_fields,但是当我使用 range 时,我不能。呼唤 rangerepack_fields 之前似乎正在分配原始数组所需的所有内存。

示例:

下面是一个示例,其中我限制了可用内存以引入我正在观察我的用例的错误。

import numpy as np
from numpy.lib.recfunctions import repack_fields
import resource

resource.setrlimit(resource.RLIMIT_AS, (int(1.5e9), int(1.5e9)))

H = np.zeros(100, dtype=[('f1', int), ('f2', int), ('large', float, 1000000)])

print('Using slicing: ')
repack_fields(H[['f1', 'f2']][0:50])
print('Using range: ')
repack_fields(H[['f1', 'f2']][range(0, 50)])

产生输出:

Using slicing: 
Using range: 
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    repack_fields(H[['f1', 'f2']][range(0, 50)])
MemoryError: Unable to allocate 381. MiB for an array with shape (50,) and data type {'names':['f1','f2'], 'formats':['<i8','<i8'], 'offsets':[0,8], 'itemsize':8000016}

问题:

  1. 为什么 range(0, 50) 的行为与 0:50 不同? (列表也不起作用。)我知道在上面的示例中,可以先重新打包字段,然后再引用行。 (也就是说,repack_fields(H[['f1', 'f2']])[range(0, 50)] 有效。)但我不想知道是先获取行还是先获取字段更好。

  2. 从大型 numpy 结构化数组中获取 rows/fields 的子集的正确方法是什么? (即使行不连续)?

repack_fields(H[['f1', 'f2']][0:50])

[['f1', 'f2']][0:50] 都产生一个 view,一个是因为它是一个多字段索引,另一个是因为它是一个切片(基本索引)。所以这不需要新的内存。 repack_fields 为这 2 个字段和 50 条记录创建了一个 space 的新数组,并从 view.

复制值
repack_fields(H[['f1', 'f2']][range(0, 50)])

同样,字段索引是一个 view,引用整个结构,包括 large 字段。 [range...] 是高级索引,制作包含 'large'.

的 50 条记录的副本

查看错误:

 Unable to allocate 381. MiB for an array with shape (50,) and data type
 {'names':['f1','f2'], 'formats':['<i8','<i8'], 'offsets':[0,8], 'itemsize':8000016}

 In [336]: 50*8000016/1e6
 Out[336]: 400.0008

它正在尝试分配 381 MB。错误发生在 [range(50)] 索引中。还没有到重新包装。

所以你必须明白两件事。

  • 使用切片进行索引使得 view 不消耗额外的内存。使用 range 或列表(或数组)进行索引是 'advanced indexing' 并制作副本。

  • 多字段索引构成 view。即使字段是源的子集,项目大小仍然是原始大小。 repack 的目的是创建一个具有新数据类型和新项目大小的新数组,仅包含所选字段的值。