在Python进程之间共享一个大的内存数据结构?

4
我们在Linux服务器上运行约10个Python进程,它们都读取同一个大型数据结构(这恰好是一个Pandas DataFrame,本质上是一个2D numpy矩阵)。
这些进程必须尽可能快地响应查询,将数据保存在磁盘上对我们的需求来说已经不够快了。
我们真正需要的是让所有进程都能完全随机访问内存中的数据结构,以便它们可以检索执行任意计算所需的所有元素。
由于其大小,我们无法将数据结构在内存中复制10次(甚至两次)。
是否有一种方法可以使所有10个Python进程共享对内存中数据结构的随机访问?

1
这些进程只是从DataFrame中读取数据,还是既读又写(修改DataFrame)?你使用的操作系统是什么? - unutbu
@unutbu:他们只是在读取数据 - 数据是由另一个进程编写的,并且一旦写入后基本上是静态和只读的。此外,这是Linux(Ubuntu Server 14.04)。 - Dun Peal
2个回答

7

因为Linux支持Copy-on-Write(COW)在fork()时,数据只有在写入时才会被复制。

因此,如果你在全局命名空间中定义DataFrame df,那么你可以从后续生成的任意子进程中访问它,而不需要为DataFrame分配额外的内存。

只有当其中一个子进程修改了df(或与df相同内存页上的数据)时,该数据(在该内存页上)才会被复制。

因此,尽管听起来很奇怪,但在Linux上共享大型内存数据结构的访问权限除了在生成子进程之前在全局命名空间中定义数据之外,你无需进行任何特殊操作。

这里有一些代码演示Copy-on-Write的行为。


当数据被修改时,它所在的内存页面会被复制。如this PDF所述:
每个进程都有一个页表,将其虚拟地址映射到物理地址;执行fork()操作时,为新进程创建一个新的页表,在其中标记每个条目为“写时复制”标志;对于调用者的地址空间也是如此。当需要更新内存内容时,检查该标志。如果设置了该标志,则分配一个新页面,将旧页面的数据复制到新页面上,进行更新,并清除新页面的“写时复制”标志。
因此,如果内存页上的某个值有更新,那么该页将被复制。如果大型DataFrame的一部分驻留在该内存页上,则只会复制该部分,而不是整个DataFrame。默认情况下,页面大小通常为4 KiB,但可能更大,具体取决于MMU的配置方式

类型

% getconf PAGE_SIZE
4096

在您的系统上查找页面大小(以字节为单位)。


谢谢!关于COW是否有任何特殊规定?例如,如果顶级__main__进程包含大量全局数据。您链接的COW解释提到了“内存页面”。是否可能其中一个分叉进程最终会触发对df的完全复制,仅因为它试图(超)写入从__main__继承的更小的数据结构之一? - Dun Peal
1
@DunPeal:只有受影响的内存页面被复制。我已经在上面添加了更多细节。 - unutbu
太好了!最后一个问题:我是否必须像您的示例中那样使用multiprocessing.Process来生成这些工作进程,以便它们可以访问__main__的全局范围,或者还有其他方法可以提供此类访问权限?例如,是否有任何方法可以使用不那么花哨的subprocess.Popen使其正常工作? - Dun Peal
1
你需要使用multiprocessing或(在Python3中)concurrent.futures这里有一个很棒的教程,可以帮助你入门。 - unutbu

0

另一种解决方案存在一个重大问题,即需要数据框在所有时候都保持静态,并且需要Linux。

您应该看一下Redis,虽然它不如本机Python内存结构快,但在相同的机器上查询类似表格的对象仍然非常快速。如果您想通过共享内存结构获得最终速度,请查看新兴的Apache Arrow 项目。

这两个选项都提供了动态数据功能和跨语言支持的重要优势,而Redis还允许您进行多节点操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接