【问题标题】:python: sharing huge dictionaries using multiprocessingpython:使用多处理共享庞大的字典
【发布时间】:2011-05-30 21:35:12
【问题描述】:

我正在处理大量数据,存储在字典中,使用多重处理。基本上我所做的只是加载一些签名,存储在字典中,从中构建一个共享的 dict 对象(获取 Manager.dict() 返回的“代理”对象)并将这个代理作为参数传递给具有在多进程中执行。

澄清一下:

signatures = dict()
load_signatures(signatures)
[...]
manager = Manager()
signaturesProxy = manager.dict(signatures)
[...]
result = pool.map ( myfunction , [ signaturesProxy ]*NUM_CORES )

现在,如果 signatures 的条目少于 200 万个左右,则一切正常。无论如何,我必须处理具有 5.8M 键的字典(以二进制格式腌制 signatures 会生成一个 4.8 GB 的文件)。在这种情况下,进程会在创建代理对象期间终止:

Traceback (most recent call last):
  File "matrix.py", line 617, in <module>
signaturesProxy = manager.dict(signatures)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 634, in temp
token, exp = self._create(typeid, *args, **kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 534, in _create
id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 79, in dispatch
raise convert_to_error(kind, result)
multiprocessing.managers.RemoteError: 
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 173, in handle_request
    request = c.recv()
EOFError
---------------------------------------------------------------------------

我知道数据结构很大,但我正在使用配备 32GB RAM 的机器,运行 top 我看到该进程在加载签名后占用 7GB RAM。然后它开始构建代理对象,RAM 使用量上升到 ~17GB RAM,但从未接近 32。此时,RAM 使用量开始迅速减少,进程因上述错误而终止。所以我猜这不是由于内存不足错误......

有什么想法或建议吗?

谢谢,

大卫

【问题讨论】:

    标签: python dictionary multiprocessing shared-objects


    【解决方案1】:

    我认为您遇到的问题是字典或哈希表在增长时会自行调整大小。最初,dict 有一定数量的可用桶。我不确定 Python,但我知道 Perl 以 8 开头,然后当存储桶已满时,哈希会再重新创建 8 个(即 8、16、32,...)。

    桶是哈希算法的着陆位置。 8 个插槽并不意味着 8 个条目,它意味着 8 个内存位置。添加新项目时,会为该密钥生成一个哈希,然后将其存储到该存储桶中。

    这就是碰撞发挥作用的地方。存储桶中的项目越多,函数的运行速度就越慢,因为插槽的动态大小会按顺序附加项目。

    可能出现的一个问题是您的密钥非常相似并且产生相同的哈希结果 - 这意味着大多数密钥都在一个插槽中。预先分配哈希桶将有助于消除这种情况并实际上改善处理时间和密钥管理,而且它不再需要进行所有交换。

    但是,我认为您仍然受限于可用的连续内存量,最终需要使用数据库解决方案。

    旁注:我还是 Python 新手,我知道在 Perl 中您可以通过 print %HASHNAME 查看哈希统计信息,它会显示您的存储桶使用情况分布。帮助您识别冲突计数 - 以防您需要预先分配存储桶。这也可以在 Python 中完成吗?

    丰富

    【讨论】:

      【解决方案2】:

      如果字典是只读的,在大多数操作系统中您不需要代理对象。

      只需在启动工作程序之前加载字典,并将它们放在可以访问的地方;最简单的地方是全局到一个模块。工人可以读取它们。

      from multiprocessing import Pool
      
      buf = ""
      
      def f(x):
          buf.find("x")
          return 0
      
      if __name__ == '__main__':
          buf = "a" * 1024 * 1024 * 1024
          pool = Pool(processes=1)
          result = pool.apply_async(f, [10])
          print result.get(timeout=5)
      

      这仅使用 1GB 内存,而不是每个进程使用 1GB,因为任何现代操作系统都会对分叉之前创建的数据进行写时复制影子。请记住,其他工作人员不会看到对数据的更改,并且内存当然会分配给您更改的任何数据。

      它将使用一些内存:每个包含引用计数的对象的页面将被修改,因此将被分配。这是否重要取决于数据。

      这适用于任何实现普通分叉的操作系统。它不适用于 Windows;它的(残废的)流程模型需要为每个工人重新启动整个流程,因此它不太擅长共享数据。

      【讨论】:

      • 这是否适用于 Windows 7(这绝对是现代操作系统?)
      • @Seun:我不知道;尝试测试它。我怀疑它的流程模型是否比以前的版本更现代。 Windows 在这方面一直处于黑暗时代。
      • 我不认为多处理使用写时复制。根据我的经验,数据将在每个子流程中重复,即使它是只读的。这个姿势似乎证实了:stackoverflow.com/q/659865/5475
      • 否决了您的答案,但赞成了您的评论(我同意!)。 :)
      • @ibz python 文档(在该问题的答案之一中引用)不同意您并同意 Glenn。见docs.python.org/dev/library/…“显式将资源传递给子进程”
      【解决方案3】:

      为什么不用数据库试试这个呢?数据库不限于可寻址/物理内存,对于多线程/进程使用是安全的。

      【讨论】:

        【解决方案4】:

        为了节省时间和不必调试系统级问题,也许您可​​以将 580 万条记录字典分成三组,每组约 200 万,然后运行 ​​3 次作业。

        【讨论】:

        • 我可以,但这不是最佳解决方案,因为无论如何,最后我必须重建整个字典并将其用于其他操作
        • 那么听起来您的任务适合 Hadoop/MapReduce...也许您应该检查一下。
        猜你喜欢
        • 1970-01-01
        • 2022-10-15
        • 2022-11-12
        • 2021-06-23
        • 2020-06-12
        • 1970-01-01
        • 1970-01-01
        • 2020-11-13
        • 2016-08-31
        相关资源
        最近更新 更多