在多个进程之间共享非常大的字典而无需复制的最快方法

Cal*_*lum 6 python python-multiprocessing

TL;DR:如何以高性能的方式在多个进程之间共享大型(200MB)只读字典,该字典的访问量非常大,而每个进程在内存中都没有完整的副本。

编辑:看起来如果我只是将字典作为 multiprocessing.Pool/Process 的参数传递,它实际上不会创建副本,除非工作人员修改字典。我只是假设它会复制。这种行为似乎仅适用于 fork 可用的 Unix,但并不总是如此。但如果是这样,它应该可以解决我的问题,直到将其转换为 ETL 作业。

我正在尝试做的事情:

我的任务是改进将数据从一个商店复制到另一个商店的脚本。途中对数据进行标准化和转换。此任务的工作规模约为 1 亿个文档,这些文档来自源文档存储,这些文档被汇总并推送到另一个目标文档存储。

每个文档都有一个 ID,还有另一个文档存储,本质上是这些 ID 的键值存储,映射到此任务所需的一些附加信息。该存储要小得多,并且在来自主存储的文档通过时对其进行查询,如果没有大量缓存,那么这并不是一个真正的选择,并且大量缓存最终很快就会成为整个事物的副本。我只是在开始任何事情之前从整个商​​店创建整个字典字典并使用它。该字典大小约为 200MB。请注意,这本词典只能被读取。

为此,我设置了多处理并拥有大约 30 个并发进程。我对每个流程的工作进行了划分,以便每个流程都有不同的指标,并且可以在大约 4 小时内完成整个工作。

我注意到,在执行以下两件事时,我的 CPU 受到极大限制:

  • 使用线程池/线程(我当前正在做的事情),这样每个线程都可以毫无问题地访问字典。GIL 快要了我的命了,我有一个进程一直在 100% 最大化,而其他 CPU 则处于空闲状态。切换到 PyPy 有很大帮助,但我仍然对这种方法不满意。
  • 为大字典创建 Multiprocessing.Manager().dict() 并让子进程通过它进行访问。此方法创建的服务器进程始终处于 100% cpu 状态。我不知道为什么,因为我只读过这本字典,所以我怀疑它是一个锁定的东西。我不知道管理器内部是如何工作的,但我猜测子进程在每次获取时都通过管道/套接字进行连接,而且这样做的开销是巨大的。它还表明,如果属实,使用 Reddis/Memcache 也会出现同样的问题。也许可以配置得更好?

做这些事情时我会受到内存限制:

  • 使用共享内存视图。您似乎无法像我需要的那样对听写执行此操作。我可以序列化字典以进入共享视图,但为了使其可在子进程上使用,您需要将数据序列化为实际可用的字典,该字典在进程中创建副本。

我强烈怀疑,除非我错过了一些东西,否则我将不得不“下载更多内存”或从Python重写为没有GIL的东西(或者使用ETL,就像应该在...中完成的那样)。

对于 ram 来说,存储这样的字典以减少其刺痛的最有效方法是什么?它目前是映射到由 3 个长整型/浮点组成的额外信息元组的标准字典。

doc_to_docinfo = { "ID1": (5.2, 3.0, 455), }

对于这个用例,是否有比我正在做的更有效的哈希图实现?

小智 1

你似乎也有和我类似的问题。可以使用我的源代码每个线程创建这些字典键的分区。我的建议:将文档 ID 拆分为长度为 3 或 4 的分区,使所有进程/线程的分区表保持同步,然后将文档的部分移动到每个进程/线程,并作为入口点,进程进行字典查找并找出哪个进程可以处理该字典的部分。如果您善于平衡分区,那么每个线程也可以管理相同数量的文档。