Python 多进程共享内存与使用参数

Python multiprocess share memory vs using arguments

我正在努力了解在不同进程之间共享相同数据源的最有效和内存消耗最少的方法。

想象下面的代码,它简化了我的问题。

import pandas as pd
import numpy as np
from multiprocessing import Pool

# method #1
def foo(i): return data[i]
if __name__ == '__main__':
    data = pd.Series(np.array(range(100000)))
    pool = Pool(2)
    print pool.map(foo,[10,134,8,1])

# method #2
def foo((data,i)): return data[i]
if __name__ == '__main__':
    data = pd.Series(np.array(range(100000)))
    pool = Pool(2)
    print pool.map(foo,[(data,10),(data,134),(data,8),(data,1)])

在第一个方法中将使用全局变量(不适用于 Windows,仅适用于 Linux/OSX),然后将由函数访问。在第二种方法中,我将 "data" 作为参数的一部分传递。

在过程中使用的内存方面,两种方式会有区别吗?

# method #3
def foo((data,i)): return data[i]
if __name__ == '__main__':
    data = pd.Series(np.array(range(100000)))
    pool = Pool(2)
    # reduce the size of the argument passed
    data1 = data[:1000]
    print pool.map(foo,[(data1,10),(data1,134),(data1,8),(data1,1)])

第三种方法,而不是传递所有 "data",因为 我们知道 我们将只使用第一条记录,我只传递第一条1000条记录。这会有什么不同吗?

背景 我面临的问题我有一个大约 200 万行(4GB 内存)的大数据集,然后将由四个子进程进行一些详细说明。每个细节只影响一小部分数据(20000 行),我想尽量减少每个并发进程的内存使用。

我将从第二种和第三种方法开始,因为它们更容易解释。

当您将参数传递给 pool.mappool.apply 时,参数将被 pickle,使用管道发送到 child 进程,然后在 child.这当然需要您传递的数据结构的两个完全不同的副本。它还会导致大型数据结构的性能下降,因为 pickling/unpickling 大型 object 可能需要相当长的时间。

使用第三种方法,您只需传递比方法二更小的数据结构。这应该会表现得更好,因为您不需要 pickle/unpickle 那么多的数据。

另一个注意事项 - 多次传递 data 绝对不是一个好主意,因为每个副本都会重复得到 pickled/unpickled。您希望将它传递给每个 child 一次。方法 1 是一个很好的方法,或者您可以使用 initializer 关键字参数显式地将 data 传递给 child。这将在 Linux 上使用 fork 并在 Windows 上进行酸洗以将数据传递给 child 进程:

import pandas as pd
import numpy as np
from multiprocessing import Pool

data = None

def init(_data):
    global data
    data = _data  # data is now accessible in all children, even on Windows

# method #1
def foo(i): return data[i]
if __name__ == '__main__':
    data = pd.Series(np.array(range(100000)))
    pool = Pool(2, initializer=init, initargs=(data,))
    print pool.map(foo,[10,134,8,1])

使用第一种方法,您利用 fork 的行为允许 child 进程继承 data object。 fork 具有 copy-on-write 语义,这意味着内存实际上在 parent 和它的 children 之间共享,直到您尝试在 [=43= 中写入它].当您尝试写入时,必须复制包含您尝试写入的数据的内存页,以使其与 parent 版本分开。

现在,这听起来像是灌篮高手 - 只要我们不写入任何内容,就无需复制任何内容,这肯定比 pickle/unpickle 方法快。通常情况就是这样。然而,在实践中,Python 是在内部写入它的 objects,即使你并不真正期望它这样做。因为 Python 使用引用计数进行内存管理,所以每次传递给方法或分配给变量等时,它都需要在每个 object 上递增内部引用计数器。所以,这意味着内存页包含传递给您的 child 进程的每个 object 的引用计数将最终被复制。这肯定会比酸洗 data 多次更快并且使用更少的内存,但也不是完全共享。