Можно ли отложить операции с 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)