Можно ли отложить операции с numpy.memmap?

Рассмотрим этот пример:

import numpy as np
a = np.array(1)
np.save("a.npy", a)

a = np.load("a.npy", mmap_mode='r')
print(type(a))

b = a + 2
print(type(b))

какие выводы

<class 'numpy.core.memmap.memmap'>
<class 'numpy.int32'>

Так что кажется, что b это не memmap больше, и я предполагаю, что это заставляет numpy читать целиком a.npy, победив цель меммапа. Отсюда мой вопрос, могут ли операции на memmaps быть отложено до времени доступа?

Я считаю, что подкласс ndarray или же memmap может сработать, но не чувствую себя достаточно уверенно в моих навыках Python, чтобы попробовать.

Вот расширенный пример, показывающий мою проблему:

import numpy as np

# create 8 GB file
# np.save("memmap.npy", np.empty([1000000000]))

# I want to print the first value using f and memmaps


def f(value):
    print(value[1])


# this is fast: f receives a memmap
a = np.load("memmap.npy", mmap_mode='r')
print("a = ")
f(a)

# this is slow: b has to be read completely; converted into an array
b = np.load("memmap.npy", mmap_mode='r')
print("b + 1 = ")
f(b + 1)

2 ответа

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

import numpy as np  
class Defered(np.ndarray):
      """
      An array class that deferrs calculations applied to it, only
      calculating them when an index is requested
      """
      def __new__(cls, arr):
            arr = np.asanyarray(arr).view(cls)
            arr.toApply = []
            return arr

      def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
            ## Convert all arguments to ndarray, otherwise arguments
            # of type Defered will cause infinite recursion
            # also store self as None, to be replaced later on
            newinputs = []
            for i in inputs:
                  if i is self:
                        newinputs.append(None)
                  elif isinstance(i, np.ndarray):
                        newinputs.append(i.view(np.ndarray))
                  else:
                        newinputs.append(i)

            ## Store function to apply and necessary arguments
            self.toApply.append((ufunc, method, newinputs, kwargs))
            return self

      def __getitem__(self, idx):
            ## Get index and convert to regular array
            sub = self.view(np.ndarray).__getitem__(idx)

            ## Apply stored actions
            for ufunc, method, inputs, kwargs in self.toApply:
                  inputs = [i if i is not None else sub for i in inputs]
                  sub = super().__array_ufunc__(ufunc, method, *inputs, **kwargs)

            return sub

Это потерпит неудачу, если в него будут внесены изменения, которые не используют универсальные функции numpy. Например percentile а также median не основаны на ufuncs, и в итоге загрузят весь массив. Аналогично, если вы передадите его функции, которая выполняет итерацию по массиву, или применяет индекс к значительным объемам, будет загружен весь массив.

Вот как работает Python. По умолчанию числовые операции возвращают новый массив, поэтому b никогда не существует в качестве memmap - он создается, когда + называется на a,

Есть несколько способов обойти это. Самое простое - сделать все операции на месте,

a += 1

Это требует загрузки массива сопоставленной памяти для чтения и записи,

a = np.load("a.npy", mmap_mode='r+')

Конечно, это не очень хорошо, если вы не хотите перезаписывать исходный массив.
В этом случае вам нужно указать, что b должен быть записан.

b = np.memmap("b.npy", mmap+mode='w+', dtype=a.dtype, shape=a.shape)

Назначение может быть сделано с помощью out ключевое слово предоставлено numpy ufuncs.

np.add(a, 2, out=b)
Другие вопросы по тегам