在 Python 进程之间共享内存中的大型数据结构



我们有大约 10 个 Python 进程在 Linux 机器上运行,它们都读取相同的大型数据结构(恰好是 Pandas DataFrame,本质上是一个 2D numpy 矩阵)。

这些进程必须尽快响应查询,将数据保留在磁盘上已经不够快,无法满足我们的需求。

我们真正需要的是让所有进程都能完全随机访问内存中的数据结构,以便它们可以检索执行任意计算所需的所有元素。

由于其大小,我们不能在内存中复制数据结构 10 次(甚至两次)。

有没有办法让所有 10 个 Python 进程共享对内存中数据结构的随机访问?

因为Linux在fork()上支持写入时复制(COW),所以不会复制数据除非写信给。

因此,如果在全局命名空间中定义数据帧df,则可以从任意数量的后续生成的子进程中访问它,并且不需要额外的数据帧内存。

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

因此,尽管听起来很奇怪,但除了在生成子进程之前定义全局命名空间中的数据外,您不必 Linux 上做任何特殊的事情来在子进程之间共享对大型内存中数据结构的访问。

下面是一些演示写入时复制行为的代码。


修改数据时,将复制其所在的内存页。如此 PDF 中所述:

每个进程都有一个页表,用于将其虚拟地址映射到 物理地址;当执行 fork() 操作时,新的 进程创建了一个新的页表,其中每个条目都标记了 带有"写入时复制"标志;这也是为调用方的 地址空间。 当要更新内存的内容时, 标志已选中。如果已设置,则分配一个新页面,数据来自 复制旧页面,在新页面上进行更新,并且 清除新页面的"写入时复制"标志。

因此,如果内存页上的某个值有更新,则该页面是复制。如果大型数据帧的一部分驻留在该内存页上,则仅复制该部分,而不是整个数据帧。默认情况下,页面大小通常为 4 KiB,但可能会更大,具体取决于 MMU 的配置方式。

类型

% getconf PAGE_SIZE
4096

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

另一种解决方案存在相当大的问题,即要求数据帧始终是静态的,并且需要Linux。

你应该看看 Redis,虽然它不像原生 python 内存结构那么快,但在同一台机器上查询类似表的对象仍然非常快。如果你想通过共享内存结构实现终极速度,请查看新生的Apache Arrow项目。

两者都提供了动态数据功能和跨语言支持的巨大优势,而 Redis 还允许您使用多节点。

最新更新