多进程全局变量内存复制

14

我正在运行一个程序,首先将20 GB的数据加载到内存中。然后,我将执行N(> 1000)个独立的任务,每个任务可能会使用(只读)20 GB数据的一部分。现在我正在尝试通过多进程来执行这些任务。但是,正如这个答案所说,每个进程都会复制整个全局变量。在我的情况下,由于我的内存只有96 GB,无法执行超过4个任务。我想知道是否有解决这种问题的方法,以便我可以充分利用所有的核心而不消耗太多内存。


1
那么每个进程都需要处理整个数据,但是它们各自做自己的事情,即没有重复努力的可能?在这种情况下,您可以尝试在Manager.dict() hereManager.list()中共享数据并生成多个Processes。如果它们都在执行相同的任务,则可以将数据分块并将每个进程分配到自己的块。我认为您不能有用地生成比核心更多的进程,而且似乎您想要>1000个进程? - roganjosh
2
@whan - 不是这样的。子进程获得了父进程内存空间的写时复制视图。只要在启动进程之前加载数据集,并且不在多进程调用中传递对该内存空间的引用(即,工作进程应直接使用全局变量),则不会进行复制。 - tdelaney
1
@tdelaney 但这只适用于Linux吗?我记得在Windows上这种事情对我失败了,但我也试图修改单个对象,而且我读到的所有内容都表明每个子对象都会得到一份副本(无论操作系统如何)。Manager 对我来说是解决方法,无论我需要读取还是写入访问权限。我将不得不再次尝试在Linux上使用这种方法。 - roganjosh
1
@roganjosh - 在Linux中,派生的子进程会获得父进程的写时复制视图。如果您修改数据,则父进程将无法看到它。我在我的答案中举了一个例子。 - tdelaney
1
@whan - 那个链接的问题是关于返回结果需要复制的,即使在Linux情况下也是如此。您可以通过将结果集切割为可pickle项(请参阅pickle ref)来避免pickle问题。那个问题还有另一个错误在于 j = multiprocessing.Process(target=getDV04CclDrivers, args=('LORR', dataDV04)) ... 即使 dataDV04 是全局的,他仍然将其作为参数传递,因此即使在Linux情况下也需要进行pickle。 - tdelaney
显示剩余10条评论
1个回答

10
在Linux中,fork的进程具有父进程地址空间的写时复制视图。fork是轻量级的,同一个程序在父进程和子进程中运行,除了子进程采取不同的执行路径。以一个小例子为例,
import os
var = "unchanged"
pid = os.fork()
if pid:
    print('parent:', os.getpid(), var)
    os.waitpid(pid, 0)
else:
    print('child:', os.getpid(), var)
    var = "changed"

# show parent and child views
print(os.getpid(), var)

结果在

parent: 22642 unchanged
child: 22643 unchanged
22643 changed
22642 unchanged

将这个应用到多进程中,在这个例子中我将数据加载到全局变量中。由于Python会对发送到进程池的数据进行序列化,因此我确保它只序列化一些小的内容,如索引,并让工作者自己获取全局数据。

import multiprocessing as mp
import os

my_big_data = "well, bigger than this"

def worker(index):
    """get char in big data"""
    return my_big_data[index]

if __name__ == "__main__":
    pool = mp.Pool(os.cpu_count())
    for c in pool.imap_unordered(worker, range(len(my_big_data)), chunksize=1):
        print(c)

在Windows中,没有像fork-and-exec模型那样运行程序。它必须启动一个新的Python解释器实例,并将所有相关数据克隆到子进程中。这是一项沉重的任务!


啊哈。好的,我理解这个问题的关键是写时复制;如果你没有更深入的了解,阅读有关mp的内容时会感到几件事情相互矛盾。感谢您的解释,点赞。出于好奇,由于Windows的限制,我仍然被迫使用“管理器”;在我的情况下,这是否会首先创建另一个20GB的对象(原始+“管理器”=40GB)以初始化共享资源?文档表明,进程通过代理访问共享列表/字典到_Manager_,但我从未在mp中处理过足够大的数据来进行测试。 - roganjosh
感谢@tdelaney,结论是否取决于Python版本(对我来说是2.7)和实现方式(pool.map,imap,imap_unordered)? - Warren
1
@whan - mapimap都会等待所有处理完成并按提交顺序返回结果。如果您不关心返回顺序,则imap_unordered更有效,并且如果结果很大,它将使用更少的内存。在Python 2.x和3.x中,多进程处理基本相同。根据您是否需要有序结果,mapimap_unordered都是不错的选择。 - tdelaney
1
@roganjosh - 我不确定Manager是如何实现的,但那些代理对象对我来说似乎很重。在Windows中,如果您可以使用共享内存或内存映射文件,我认为您会更好。但这意味着像列表(它们分散在整个内存中)这样的东西是一个问题。从哲学上讲,Windows 更喜欢线程而不是轻量级子进程,但这与 Python GIL 模型相矛盾。 - tdelaney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接