如何使具有相同cython memoryview的许多视图的对象腌制

时间:2019-10-11 14:14:19

标签: cython pickle

我尝试腌制的许多对象都具有相同的(大)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。

1 个答案:

答案 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可能不是“与实际的内存视图相同。