如何使用共享内存而不是通过多个进程之间的酸洗传递对象

时间:2018-12-04 15:16:57

标签: python multiprocessing threadpool

我正在处理一个CPU密集型ML问题,该问题围绕一个附加模型。由于加法是主要操作,因此我可以将输入数据分成多个部分,并生成多个模型,然后通过覆盖的__add__方法将其合并。

与多处理相关的代码如下:

def pool_worker(filename, doshuffle):
    print(f"Processing file: {filename}")
    with open(filename, 'r') as f:
        partial = FragmentModel(order=args.order, indata=f, shuffle=doshuffle)
        return partial

def generateModel(is_mock=False, save=True):
    model = None
    with ThreadPool(args.nthreads) as pool:
        from functools import partial
        partial_models = pool.imap_unordered(partial(pool_worker, doshuffle=is_mock), args.input)
        i = 0
        for m in partial_models:
            logger.info(f'Starting to merge model {i}')
            if model is None:
                import copy
                model = copy.deepcopy(m)
            else:
                model += m
            logger.info(f'Done merging...')
            i += 1

    return model

问题在于,内存消耗随着模型顺序的增加而呈指数级增长,因此,在顺序4时,模型的每个实例约为4-5 GB,这会导致线程池崩溃,因为此时中间模型对象不可腌制。

我稍微了解了一下,看来即使酸洗不是问题,但传递这样的数据仍然效率极低,正如对this answer的评论。

但是,关于如何为此目的使用共享内存的指导很少。是否可以避免此问题而不必更改模型对象的内部?

5 个答案:

答案 0 :(得分:5)

使用文件!

不,实际上,使用文件是有效的(操作系统将缓存内容),并允许您处理更大的问题(数据集不必放入RAM)。

使用https://docs.scipy.org/doc/numpy-1.15.0/reference/routines.io.html中的任何一个将numpy数组转储到文件中/从文件中加载numpy数组,并且仅在进程之间传递文件名。

P.S。基准序列化方法,取决于中间阵列的大小,最快的可能是“原始”(无转换开销)或“压缩”(如果文件最终被写入磁盘)或其他。 IIRC加载“原始”文件可能需要事先知道数据格式(尺寸,大小)。

答案 1 :(得分:1)

检出ray项目,它是一个分布式执行框架,利用apache arrow进行序列化。如果您正在使用numpy数组,则特别好,因此它是ML工作流的绝佳工具。

这是object serialization

上的文档的摘录
  

在Ray中,我们通过使用Apache Arrow数据优化numpy数组   格式。当我们反序列化对象的numpy数组列表时   存储,我们仍然创建一个numpy数组对象的Python列表。然而,   而不是复制每个numpy数组,每个numpy数组对象都包含一个   指向共享内存中保存的相关数组的指针。有一些   这种序列化的优点。

     
      
  • 反序列化可能非常快。
  •   
  • 进程之间共享内存   因此工作进程可以全部读取相同的数据,而无需复制   它。
  •   

我认为它比并行处理的多处理库更容易使用,特别是在使用共享内存时,尤其是在tutorial中使用时。

答案 2 :(得分:0)

您应将Manager代理对象用于共享的可编辑对象:https://docs.python.org/3/library/multiprocessing.html#multiprocessing-managers 访问锁定将由该Manager代理对象处理。

Customized managers部分中,有一个适合您的示例:

from multiprocessing.managers import BaseManager

class MathsClass:
    def add(self, x, y):
        return x + y
    def mul(self, x, y):
        return x * y

class MyManager(BaseManager):
    pass

MyManager.register('Maths', MathsClass)

if __name__ == '__main__':
    with MyManager() as manager:
        maths = manager.Maths()
        print(maths.add(4, 3))         # prints 7
        print(maths.mul(7, 8))         # prints 56

此后,您必须从不同的进程(如using a remote manager中)连接到该管理器,然后根据需要对其进行编辑。

答案 3 :(得分:0)

从Python 3.8(现在为Alpha版)开始,将有multiprocessing.shared_memory,它将允许进程之间直接进行读/写数据共享,类似于其他语言(C,Java)中的“真实”多线程。

与通过磁盘或套接字进行的数据共享或其他需要序列化/反序列化和复制数据的通信相比,这将更快,更容易使用。

示例:

>>> import numpy as np
>>> import multiprocessing as mp
>>> a = np.array([1, 1, 2, 3, 5, 8])  # numpy array on private memory
>>> shm = mp.shared_memory.SharedMemory(create=True, size=a.nbytes)  # allocate shared memory
>>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf)  # numpy array on shared memory
>>> b[:] = a[:]  # copy data into shared memory
>>> type(b)
<class 'numpy.ndarray'>
>>> b
array([1, 1, 2, 3, 5, 8])

答案 4 :(得分:0)

使用多线程。

尝试使用多线程而不是多处理,因为线程可以本地共享主进程的内存。

如果您担心python的GIL机制,也许可以求助于numba的{​​{3}}。