我试图了解在不同进程之间共享相同数据源的最有效和耗费内存的方法。
想象一下以下代码,它可以简化我的问题。
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上工作),然后由函数访问。在第二种方法中,我传递了数据"作为论点的一部分。
就过程中使用的内存而言,两种方法之间会有区别吗?
# 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)])
第三种方法,而不是传递所有"数据",因为我们知道我们只会使用第一条记录,我只会通过前1000条记录。这有什么不同吗?
背景 我面临的问题是我有一个大约2百万行(内存为4GB)的大数据集,然后由四个子进程进行一些详细说明。每个细化仅影响一小部分数据(20000行),并且我希望最小化每个并发进程的内存使用。
答案 0 :(得分:7)
我将从第二种和第三种方法开始,因为它们更容易解释。
当您将参数传递给pool.map
或pool.apply
时,参数将被pickle,使用管道发送到子进程,然后在子进程中进行unpickled。这当然需要两个完全不同的数据结构副本。它也可能导致大型数据结构的性能下降,因为酸洗/去除大型对象可能需要相当长的时间。
使用第三种方法,您只需传递比方法二更小的数据结构。这应该会更好,因为您不需要挑选/取消尽可能多的数据。
另一个注意事项 - 多次传递data
绝对是一个坏主意,因为每个副本都会反复进行腌制/取消。您希望将其传递给每个孩子一次。方法1是一种很好的方法,或者您可以使用initializer
关键字参数将data
显式传递给子级。这将在Linux上使用fork
并在Windows上进行pickling以将数据传递给子进程:
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
的行为来允许子进程继承data
对象。 fork
具有写时复制语义,这意味着内存实际上是在父级及其子级之间共享,直到您尝试在子级中写入它。当您尝试编写时,必须复制您尝试编写的数据所包含的内存页面,以使其与父版本分开。
现在,这听起来像一个扣篮 - 只要我们不写信就不需要复制任何内容,这肯定比pickle / unpickle方法更快。通常情况就是这样。然而,在实践中,Python在内部写入其对象,即使您不会真正期望它。因为Python使用引用计数进行内存管理,所以每次将对象传递给方法或分配给变量等时,都需要递增每个对象的内部引用计数器。因此,这意味着包含每个对象的引用计数的内存页面对您的孩子进程最终会被复制。这肯定会比腌制data
多次更快,使用更少的内存,但也不是完全共享。