我尝试腌制的许多对象都具有相同的(大)cython内存视图作为属性。由于内存视图是通过引用传递的,因此它们都共享相同的内存,并且实现内存效率高。
现在,我需要对这些对象进行腌制并重新加载它们,同时保持共享数据的共享(如果共享数据不共享,则文件大小会变大,并且无法读入内存)。通常,我认为pickle可以识别共享数据,并且只能对它们进行一次pickle / unpickle,但是由于无法直接对内存视图进行酸洗,因此需要在每个对象的 reduce 方法中将它们转换为numpy数组。 pickle不再识别数据已共享。
是否有某种方式可以通过酸洗/酸洗过程维护共享数据?
随后是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。然后“导出PYTHONPATH =“ $ PYTHONPATH”:。”然后运行
from test import main
main()
来自python。
答案 0 :(得分:2)
实际上有两个问题:
第一:共享对象只有在一次性酸洗后才能在转储/装载后共享(另请参见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数组(或显然必须支持酸洗/解酸的任何类型)的引用。
警告:正如@DavidW正确指出的那样,在使用切片的内存视图时,必须考虑其他注意事项-因为在这种情况下,base
可能不是“与实际的内存视图相同。