IPC在单独的Docker容器中的Python脚本之间共享内存

时间:2019-07-05 19:39:40

标签: python docker ipc python-multiprocessing shared-memory

问题

我编写了一个神经网络分类器,该分类器可以采集海量图像(每张图像约1-3 GB),将其打补丁,然后将补丁分别通过网络传递。培训进行的真的很慢,因此我对其进行了基准测试,发现花费大约50秒钟的时间将补丁从一个图像加载到内存(使用Openslide library),并且仅需约.5 s使它们通过模型。

但是,我正在使用1.5Tb RAM的超级计算机,其中只有约26 Gb的内存正在使用。数据集总计约500Gb。我的想法是,如果我们可以将整个数据集加载到内存中,它将极大地加快训练速度。但是我正在与一个研究团队合作,我们正在多个Python脚本之间进行实验。因此,理想情况下,我想将一个脚本中的整个数据集加载到内存中,并能够在所有脚本中对其进行访问。

更多详细信息:

  • 我们在单独的Docker容器中(在同一台机器上)运行我们的实验,因此必须跨多个容器访问数据集。
  • 数据集为Camelyon16 Dataset;图像以.tif格式存储。
  • 我们只需要阅读图像,而无需编写。
  • 我们只需要一次访问数据集的一小部分。

可能的解决方案

我发现了许多有关如何在多个Python脚本之间共享Python对象或内存中的原始数据的帖子:

跨脚本共享Python数据

在多处理模块中使用SyncManager和BaseManager的服务器进程| Example 1 | Example 2 | Docs - Server Processes | Docs - SyncManagers

  • 正数:可以由网络上不同计算机上的进程共享(可以由多个容器共享吗?)
  • 可能的问题:根据文档显示,速度比使用共享内存慢。如果我们使用客户端/服务器在多个容器之间共享内存,那会比从磁盘读取所有脚本的速度快吗?
  • 可能的问题:根据this answerManager对象在发送对象之前先腌制对象,这可能会减慢速度。

mmap模块| Docs

  • 可能的问题:mmap将文件映射到virtual memory, not physical memory-它会创建一个临时文件。
  • 可能的问题:因为我们一次只使用一小部分数据集,虚拟内存将整个数据集都放在磁盘上,所以我们遇到了thrashing问题和程序问题。

Pyro4(Python对象的客户端服务器)| Docs

Python的sysv_ipc模块。 This demo看起来很有前途。

  • 可能的问题:内置multi-processing模块中可能只有lower level exposure个可用的东西?

我还找到了this list个Python中的IPC /网络选项。

一些人讨论服务器-客户端设置,一些人讨论序列化/反序列化,我担心这将比从磁盘读取花费更多的时间。我没有找到关于这些问题是否会改善I / O性能的问题。

在Docker容器之间共享内存

我们不仅需要在脚本之间共享Python对象/内存;我们需要在Docker容器之间共享它们。

Docker documentation很好地解释了--ipc标志。根据文档运行的信息对我来说有意义:

docker run -d --ipc=shareable data-server
docker run -d --ipc=container:data-server data-client

但是,当我在如上所述设置了--ipc连接的单独容器中运行客户端和服务器时,它们无法相互通信。我读过的SO问题(1234)没有解决单独的Docker容器中Python脚本之间共享内存的集成。

我的问题:

  • 1:这些方法中的任何一种都比从磁盘读取提供更快的访问速度吗?认为跨进程/容器共享内存中的数据可以提高性能甚至合理吗?
  • 2:哪种方法最适合在多个Docker容器之间共享内存中的数据?
  • 3:如何将来自Python的内存共享解决方案与docker run --ipc=<mode>集成在一起? (共享IPC名称空间甚至是跨Docker容器共享内存的最佳方法吗?)
  • 4:是否有比这些更好的解决方案来解决我们大量I / O开销的问题?

最小工作示例-已更新。不需要外部依赖!

这是我在单独容器中的Python脚本之间共享内存的幼稚方法。当Python脚本在同一容器中运行时有效,但在单独的容器中运行时无效。

server.py

from multiprocessing.managers import SyncManager
import multiprocessing

patch_dict = {}

image_level = 2
image_files = ['path/to/normal_042.tif']
region_list = [(14336, 10752),
               (9408, 18368),
               (8064, 25536),
               (16128, 14336)]

def load_patch_dict():

    for i, image_file in enumerate(image_files):
        # We would load the image files here. As a placeholder, we just add `1` to the dict
        patches = 1
        patch_dict.update({'image_{}'.format(i): patches})

def get_patch_dict():
    return patch_dict

class MyManager(SyncManager):
    pass

if __name__ == "__main__":
    load_patch_dict()
    port_num = 4343
    MyManager.register("patch_dict", get_patch_dict)
    manager = MyManager(("127.0.0.1", port_num), authkey=b"password")
    # Set the authkey because it doesn't set properly when we initialize MyManager
    multiprocessing.current_process().authkey = b"password"
    manager.start()
    input("Press any key to kill server".center(50, "-"))
    manager.shutdown

client.py

from multiprocessing.managers import SyncManager
import multiprocessing
import sys, time

class MyManager(SyncManager):
    pass

MyManager.register("patch_dict")

if __name__ == "__main__":
    port_num = 4343

    manager = MyManager(("127.0.0.1", port_num), authkey=b"password")
    multiprocessing.current_process().authkey = b"password"
    manager.connect()
    patch_dict = manager.patch_dict()

    keys = list(patch_dict.keys())
    for key in keys:
        image_patches = patch_dict.get(key)
        # Do NN stuff (irrelevant)

当这些脚本在同一容器中运行时,这些脚本可以很好地共享图像。但是当它们在单独的容器中运行时,如下所示:

# Run the container for the server
docker run -it --name cancer-1 --rm --cpus=10 --ipc=shareable cancer-env
# Run the container for the client
docker run -it --name cancer-2 --rm --cpus=10 --ipc=container:cancer-1 cancer-env

我收到以下错误:

Traceback (most recent call last):
  File "patch_client.py", line 22, in <module>
    manager.connect()
  File "/usr/lib/python3.5/multiprocessing/managers.py", line 455, in connect
    conn = Client(self._address, authkey=self._authkey)
  File "/usr/lib/python3.5/multiprocessing/connection.py", line 487, in Client
    c = SocketClient(address)
  File "/usr/lib/python3.5/multiprocessing/connection.py", line 614, in SocketClient
    s.connect(address)
ConnectionRefusedError: [Errno 111] Connection refused

2 个答案:

答案 0 :(得分:4)

我建议您尝试使用tmpfs

这是一项Linux功能,允许您创建一个虚拟文件系统,所有文件系统都存储在RAM中。这样可以非常快速地访问文件,并且只需要设置一个bash命令即可。

除了非常快速和直截了当之外,它在您的情况下还具有许多优点:

  • 无需触摸当前代码-数据集的结构保持不变
  • 无需额外的工作来创建共享数据集-只需cp将数据集放入tmpfs
  • 通用接口-作为文件系统,您可以轻松地将RAM数据集与系统中不一定要用python编写的其他组件集成。例如,只需在容器中传递安装目录即可。
  • 将适用于其他环境-如果您的代码必须在其他服务器上运行,tmpfs可以调整页面并将其交换到硬盘驱动器上。如果您必须在没有可用RAM的服务器上运行此文件,则可以将所有文件都放在具有普通文件系统的硬盘驱动器上,而根本不用触摸代码。

使用步骤:

  1. 创建一个tmpfs-sudo mount -t tmpfs -o size=600G tmpfs /mnt/mytmpfs
  2. 复制数据集-cp -r dataset /mnt/mytmpfs
  3. 将所有引用从当前数据集更改为新数据集
  4. 享受


编辑:

在某些情况下,

ramfs可能比tmpfs更快,因为它没有实现页面交换。要使用它,只需按照上面的说明将tmpfs替换为ramfs

答案 1 :(得分:-1)

我认为shared memorymmap解决方案是正确的。

共享内存:

首先在服务器进程中读取内存中的数据集。对于python,只需使用multiprocessing包装器在进程之间的共享内存中创建对象,例如:multiprocessing.Valuemultiprocessing.Array,然后创建Process并将共享对象作为args传递。

mmap:

将数据集存储在主机上的文件中。然后,每个容器将文件安装到容器中。如果一个容器打开文件并将文件映射到其虚拟内存,则另一个容器在打开文件时将不需要从磁盘读取文件到内存,因为该文件已在物理内存中。

P.S。我不确定cpython如何在进程之间实现大型共享内存,可能cpython共享内存在内部使用mmap