【问题标题】:Python multiprocess share memory vs using argumentsPython多进程共享内存与使用参数
【发布时间】:2015-05-05 21:26:34
【问题描述】:

我正试图弄清楚在不同进程之间共享相同数据源的最有效且内存消耗更少的方法是什么。

想象一下下面的代码,它可以简化我的问题。

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 条记录。这会有什么不同吗?

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

【问题讨论】:

    标签: python pandas dataset multiprocessing shared-memory


    【解决方案1】:

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

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

    使用第三种方法,您只需传递比方法二更小的数据结构。这应该会表现得更好,因为您不需要腌制/取消腌制尽可能多的数据。

    另一个注意事项 - 多次传递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 更快并且使用更少的内存,但也不是完全共享的。

    【讨论】:

    • 我收到这个错误:---> 17 print pool.map(foo,[10,134,8,1], intializer=init, initargs=(data,)) TypeError: map() got an unexpected keyword argument 'intializer'multiprocessing.__version__: '0.70a1'
    • 谢谢 - 解释得很好,这将满足我的需求!
    • @Primer 糟糕,我在错误的行中添加了 initializer 参数。它属于对Pool(...) 的调用,而不是对map 的调用。我还在initializer 的拼写中打错了字。这就是我从内存中编写代码所得到的:)。无论如何,现在修好了。
    猜你喜欢
    • 1970-01-01
    • 2010-12-09
    • 2020-02-23
    • 2012-07-29
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    • 2021-09-24
    • 2012-07-03
    相关资源
    最近更新 更多