可以推迟对 numpy.memmap 的操作吗?

Can operations on a numpy.memmap be deferred?

考虑这个例子:

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,破坏了 a.npy 的目的内存映射。因此我的问题是,memmaps 上的操作可以推迟到访问时间吗?

我相信子类化 ndarraymemmap 可以工作,但对我的 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)

这就是 python 的工作原理。默认情况下,numpy 操作 return 一个新数组,因此 b 永远不会作为内存映射存在 - 它是在 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 关键字进行分配 provided by numpy ufuncs.

np.add(a, 2, out=b)

这是一个 ndarray 子类的简单示例,它推迟对其的操作,直到通过索引请求特定元素。
我包括这个是为了表明它可以完成,但它几乎肯定会以新颖和意想不到的方式失败,并且需要大量工作才能使其可用。 对于非常具体的情况,它可能比重新设计代码以更好的方式解决问题更容易。 我建议阅读文档 these examples 以帮助理解它是如何工作的。

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 的通用函数对其进行修改,这将失败。例如 percentilemedian 不是基于 ufunc,最终会加载整个数组。同样,如果您将它传递给迭代数组的函数,或将索引应用于大量的索引,整个数组将被加载。