如何用相同的 cython memoryview 的许多视图来腌制对象
How to pickle objects with many views of same cython memoryview
我有许多我试图腌制的对象,它们都共享相同的(大)cython memoryview 作为属性。由于内存视图是通过引用传递的,因此它们都共享相同的内存并且实现是内存高效的。
现在我需要 pickle 这些对象并重新加载它们,同时保持共享数据共享(如果共享数据变得不共享,那么文件大小会爆炸并且无法读入内存)。通常我认为 pickle 识别共享数据并且只 pickles/unpickles 它一次,但是因为不能直接 pickle 内存视图,所以需要在 reduce 中将它们转换为 numpy 数组每个对象和 pickle 的方法不再识别数据是共享的。
有什么方法可以通过 pickle/unpickle 进程维护共享数据?
A MWE 如下:
import numpy as np
import pickle
cdef class SharedMemory:
cdef public double[:, :] data
def __init__(self, data):
self.data = data
def duplicate(self):
return SharedMemory(self.data)
def __reduce__(self):
return self.__class__, (np.asarray(self.data),)
def main():
x = SharedMemory(np.random.randn(100, 100))
duplicates = [x.duplicate() for _ in range(5)]
cdef double* pointerx = &x.data[0, 0]
cdef double* pointerd
cdef double[:, :] ddata
for d in duplicates:
ddata = d.data
pointerd = &ddata[0, 0]
if pointerd != pointerx:
print('Memory is not shared')
else:
print('Memory is shared')
print('pickling')
with open('./temp.pickle', 'wb') as pfile:
pickle.dump(x, pfile, protocol=pickle.HIGHEST_PROTOCOL)
for d in duplicates:
pickle.dump(d, pfile, protocol=pickle.HIGHEST_PROTOCOL)
with open('./temp.pickle', 'rb') as pfile:
nx = pickle.load(pfile)
nd = []
for d in duplicates:
nd.append(pickle.load(pfile))
ddata = nx.data
cdef double* pointernx = &ddata[0, 0]
for d in nd:
ddata = d.data
pointerd = &ddata[0, 0]
if pointerd != pointernx:
print('Memory is not shared')
else:
print('Memory is shared')
将以上内容放入文件 test.pyx 中,使用 "cythonize -a -i test.pyx" 进行 cythonize。然后 "export PYTHONPATH="$PYTHONPATH":"和 运行
from test import main
main()
来自 python。
其实有两个问题:
首先: 共享对象在 dump/load 之后也被共享,只有当它们被一次腌制时(另见 this answer)。
这意味着您需要执行以下操作(或类似操作):
...
with open('./temp.pickle', 'wb') as pfile:
pickle.dump((x,duplicates), pfile, protocol=pickle.HIGHEST_PROTOCOL)
...
with open('./temp.pickle', 'rb') as pfile:
nx, nd = pickle.load(pfile)
...
当您转储单个对象时,pickle 无法跟踪相同的对象 - 这样做会成为一个问题:两个 dump
调用之间具有相同 ID 的对象可能是完全不同的对象,也可能是相同的对象不同内容的对象!
其次: 你不应该创建新对象,而是在 __reduce__
中传递共享的 numpy 对象(pickle 不会查看 numpy 数组内部以看,缓冲区是否共享,但仅在数组的 id 处):
def __reduce__(self):
return self.__class__, (self.data.base,)
这会给你想要的结果。 data.base
是对底层原始 numpy 数组(或任何类型,显然必须支持 pickling/unpickling)的引用。
警告: 正如@DavidW 正确指出的那样,在使用切片内存视图时必须考虑其他注意事项 - 因为在这种情况下 base
可能不是 "the same" 作为实际内存视图。
我有许多我试图腌制的对象,它们都共享相同的(大)cython memoryview 作为属性。由于内存视图是通过引用传递的,因此它们都共享相同的内存并且实现是内存高效的。
现在我需要 pickle 这些对象并重新加载它们,同时保持共享数据共享(如果共享数据变得不共享,那么文件大小会爆炸并且无法读入内存)。通常我认为 pickle 识别共享数据并且只 pickles/unpickles 它一次,但是因为不能直接 pickle 内存视图,所以需要在 reduce 中将它们转换为 numpy 数组每个对象和 pickle 的方法不再识别数据是共享的。
有什么方法可以通过 pickle/unpickle 进程维护共享数据?
A MWE 如下:
import numpy as np
import pickle
cdef class SharedMemory:
cdef public double[:, :] data
def __init__(self, data):
self.data = data
def duplicate(self):
return SharedMemory(self.data)
def __reduce__(self):
return self.__class__, (np.asarray(self.data),)
def main():
x = SharedMemory(np.random.randn(100, 100))
duplicates = [x.duplicate() for _ in range(5)]
cdef double* pointerx = &x.data[0, 0]
cdef double* pointerd
cdef double[:, :] ddata
for d in duplicates:
ddata = d.data
pointerd = &ddata[0, 0]
if pointerd != pointerx:
print('Memory is not shared')
else:
print('Memory is shared')
print('pickling')
with open('./temp.pickle', 'wb') as pfile:
pickle.dump(x, pfile, protocol=pickle.HIGHEST_PROTOCOL)
for d in duplicates:
pickle.dump(d, pfile, protocol=pickle.HIGHEST_PROTOCOL)
with open('./temp.pickle', 'rb') as pfile:
nx = pickle.load(pfile)
nd = []
for d in duplicates:
nd.append(pickle.load(pfile))
ddata = nx.data
cdef double* pointernx = &ddata[0, 0]
for d in nd:
ddata = d.data
pointerd = &ddata[0, 0]
if pointerd != pointernx:
print('Memory is not shared')
else:
print('Memory is shared')
将以上内容放入文件 test.pyx 中,使用 "cythonize -a -i test.pyx" 进行 cythonize。然后 "export PYTHONPATH="$PYTHONPATH":"和 运行
from test import main
main()
来自 python。
其实有两个问题:
首先: 共享对象在 dump/load 之后也被共享,只有当它们被一次腌制时(另见 this answer)。
这意味着您需要执行以下操作(或类似操作):
...
with open('./temp.pickle', 'wb') as pfile:
pickle.dump((x,duplicates), pfile, protocol=pickle.HIGHEST_PROTOCOL)
...
with open('./temp.pickle', 'rb') as pfile:
nx, nd = pickle.load(pfile)
...
当您转储单个对象时,pickle 无法跟踪相同的对象 - 这样做会成为一个问题:两个 dump
调用之间具有相同 ID 的对象可能是完全不同的对象,也可能是相同的对象不同内容的对象!
其次: 你不应该创建新对象,而是在 __reduce__
中传递共享的 numpy 对象(pickle 不会查看 numpy 数组内部以看,缓冲区是否共享,但仅在数组的 id 处):
def __reduce__(self):
return self.__class__, (self.data.base,)
这会给你想要的结果。 data.base
是对底层原始 numpy 数组(或任何类型,显然必须支持 pickling/unpickling)的引用。
警告: 正如@DavidW 正确指出的那样,在使用切片内存视图时必须考虑其他注意事项 - 因为在这种情况下 base
可能不是 "the same" 作为实际内存视图。