2

我在 python 中有一个非常有趣的案例。

主进程创建N个子进程。子流程是从multiprocessing.Process继承的类。

现在,当子进程数为10个时,每个子进程消耗大约15M的驻留内存。但是,当我将子进程数增加到100个时,每个子进程的驻留内存消耗会跃升至50M左右!!!

任何人都可以解释这种记忆跳跃/建议如何避免它?

下面是子流程类的结构:

class MySubProcess(multiprocessing.Process):
    def __init__(self, sub_process_number):
        multiprocessing.Process.__init__(self, target=self.go)

        self.m_sub_process_number = sub_process_number


    def go(self):
        self.m_config = global_config
        while (True):
        ....

非常感谢!!!

4

1 回答 1

1

当我尝试一个简单的例子时,每个子进程什么都不做,但time.sleep()我没有看到这种行为,所以我不相信这是multiprocessing模块固有的东西。

我最好的猜测是 的内存复制功能fork(),它multiprocessing很可能在后台使用。在 Unix 上 fork 一个新进程的语义要求将父进程的整个内存空间复制到子进程中。因此,假设您要MySubProcess在启动其中任何一个之前创建这些结构的列表。然后,该列表将被复制到每个子进程的地址空间中,因此当您查看每个这些进程的驻留大小时,它会显得大得多(假设您的结构占用了大量的内存)。

此外,您在启动子进程之前分配的任何其他内存都将被复制,但实例列表是我能想到的主要内容,随着您分配更多进程,它的大小会增加。根据您的代码,可能还有其他数据结构会随着进程的数量(例如工作队列)而扩展。

如果您del在每个孩子的上下文中不需要所有东西,您可能会发现它们的大小会变小,但这取决于 Python 分配器和系统内存分配器之间相当复杂的交互,所以这绝不是肯定的。从本质上讲,Python 可以保留释放的内存以供重复使用,即使 Python 解释器没有,系统分配器也可以这样做。简而言之,这可能不值得付出努力 - 有关更多信息,请参阅我的答案的结尾。

然而,这并没有看起来那么糟糕,因为 Linux(和其他现代 Unix 变体)使用所谓的写时复制语义来确保 的行为fork()不是那么低效。本质上,子进程保持对与父进程相同的内存页面的引用——只要两个进程都没有改变任何内容,内存实际上并没有被复制,尽管如果你将两个进程的内存使用数据相加,ps它将top是计算了两次,因为他们的每个进程的方法不够聪明,无法注意到页面的共享。这与拥有多个指向同一个基础文件的硬链接没有什么不同,如果您曾经遇到过这种情况的话。

一旦进程写入内存页面,它就会被复制(因此称为“写时复制”),因此将使用实际的物理内存。在这种情况下,所需的额外内存量很难预测,因为它涉及将 Python 数据结构一直映射到物理内存页面。然而,原则本身才是重要的。

您可以通过使用该实用程序显示整体系统内存使用情况并比较两种情况之间的数据来测试我的理论是否正确free- 如果我是对的,您会在 100 个子进程的情况下看到一些增加的内存,但没有那么多正如检查每个进程的内存使用情况所建议的那样。不要忘记使用第二行(即-/+ buffers/cache行)中的数字,因为这将消除两次测试之间文件系统缓存中的任何更改。

假设这是正确的,最好的办法是在父进程分配大量内存之前尽早尝试启动子进程。然而,除了你在这方面的最大努力之外,你可能不需要太担心它 - 即使页面在写入时被复制,它们也不会被子进程访问,因此将被换出到磁盘需要并且可能永远不会换回,因此不会对性能造成太大影响(除非您的平台没有任何交换)。

最后一点 - 在实践中,创建更多的工作进程可能没有什么意义,而不是机器上的核心,除非你使用非常专业的硬件,否则通常不超过 8 个或 16 个左右。如果你创建了太多的进程,那么你可能浪费了更多的时间来调度它们而不是你得到的好处——无论你做什么,你都无法获得比物理内核更多的并行化(尽管超线程会使这稍微复杂化)。

This other SO question可能会提供一些更有用的信息。

于 2013-02-05T12:06:24.863 回答