Подтвердить что ты не робот

Изменение размера массивов numpy.memmap

Я работаю с множеством больших массивов numpy, и поскольку в последнее время они начали пережевывать слишком много памяти, я хотел заменить их экземплярами numpy.memmap. Проблема в том, что время от времени мне приходится изменять размеры массивов, и я предпочел бы делать это на месте. Это работало довольно хорошо с обычными массивами, но попытка, что на memmaps жалуется, что данные могут быть разделены, и даже отключение refcheck не помогает.

a = np.arange(10)
a.resize(20)
a
>>> array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

a = np.memmap('bla.bin', dtype=int)
a
>>> memmap([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

a.resize(20, refcheck=False)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-41-f1546111a7a1> in <module>()
----> 1 a.resize(20, refcheck=False)

ValueError: cannot resize this array: it does not own its data

Изменение размера базового буфера mmap отлично работает. Проблема заключается в том, как отразить эти изменения в объекте массива. Я видел этот обходной путь, но, к сожалению, он не изменяет размер массива на месте. Существует также документация numpy об изменении размера mmaps, но она явно не работает, по крайней мере, с версией 1.8.0. Любые другие идеи, как переопределить встроенные проверки размера?

4b9b3361

Ответ 1

Проблема заключается в том, что флаг OWNDATA является False при создании массива. Вы можете изменить это, указав флаг True при создании массива:

>>> a = np.require(np.memmap('bla.bin', dtype=int), requirements=['O'])
>>> a.shape
(10,)
>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> a.resize(20, refcheck=False)
>>> a.shape
(20,)

Единственное предостережение состоит в том, что он может создать массив и сделать копию, чтобы убедиться, что требования выполнены.

Изменить сохранение адреса:

Если вы хотите сохранить массив повторного размера на диск, вы можете сохранить memmap в формате .npy в формате и открыть как numpy.memmap, когда вам нужно его повторно открыть и использовать в качестве memmap:

>>> a[9] = 1
>>> np.save('bla.npy',a)
>>> b = np.lib.format.open_memmap('bla.npy', dtype=int, mode='r+')
>>> b
memmap([0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

Изменить, чтобы предложить другой метод:

Вы можете приблизиться к тому, что вы ищете, переделав базовый mmap (a.base или a._mmap, сохраненный в формате uint8) и "перезагрузив" memmap:

>>> a = np.memmap('bla.bin', dtype=int)
>>> a
memmap([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
>>> a[3] = 7
>>> a
memmap([0, 0, 0, 7, 0, 0, 0, 0, 0, 0])
>>> a.flush()
>>> a = np.memmap('bla.bin', dtype=int)
>>> a
memmap([0, 0, 0, 7, 0, 0, 0, 0, 0, 0])
>>> a.base.resize(20*8)
>>> a.flush()
>>> a = np.memmap('bla.bin', dtype=int)
>>> a
memmap([0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

Ответ 2

Если я не ошибаюсь, это в значительной степени обеспечивает то, что делает второе решение @wwwslinger, но без необходимости вручную указывать размер новой memmap в битах:

In [1]: a = np.memmap('bla.bin', mode='w+', dtype=int, shape=(10,))

In [2]: a[3] = 7

In [3]: a
Out[3]: memmap([0, 0, 0, 7, 0, 0, 0, 0, 0, 0])

In [4]: a.flush()

# this will append to the original file as much as is necessary to satisfy
# the new shape requirement, given the specified dtype
In [5]: new_a = np.memmap('bla.bin', mode='r+', dtype=int, shape=(20,))

In [6]: new_a
Out[6]: memmap([0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

In [7]: a[-1] = 10

In [8]: a
Out[8]: memmap([ 0,  0,  0,  7,  0,  0,  0,  0,  0, 10])

In [9]: a.flush()

In [11]: new_a
Out[11]: 
memmap([ 0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,
         0,  0,  0])

Это хорошо работает, когда новый массив должен быть больше старого, но я не думаю, что этот тип подхода позволит автоматически усекать размер файла с отображением памяти, если новый массив меньше.

В ручном изменении размера базы, как и в ответе @wwwslinger, кажется, что файл усечен, но он не уменьшает размер массива.

Например:

# this creates a memory mapped file of 10 * 8 = 80 bytes
In [1]: a = np.memmap('bla.bin', mode='w+', dtype=int, shape=(10,))

In [2]: a[:] = range(1, 11)

In [3]: a.flush()

In [4]: a
Out[4]: memmap([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

# now truncate the file to 40 bytes
In [5]: a.base.resize(5*8)

In [6]: a.flush()

# the array still has the same shape, but the truncated part is all zeros
In [7]: a
Out[7]: memmap([1, 2, 3, 4, 5, 0, 0, 0, 0, 0])

In [8]: b = np.memmap('bla.bin', mode='r+', dtype=int, shape=(5,))

# you still need to create a new np.memmap to change the size of the array
In [9]: b
Out[9]: memmap([1, 2, 3, 4, 5])