我正在处理一个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的评论。
但是,关于如何为此目的使用共享内存的指导很少。是否可以避免此问题而不必更改模型对象的内部?
答案 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工作流的绝佳工具。
上的文档的摘录在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}}。