【问题标题】:IPC shared memory across Python scripts in separate Docker containers在不同的 Docker 容器中跨 Python 脚本的 IPC 共享内存
【发布时间】:2019-11-16 09:47:15
【问题描述】:

问题

我编写了一个神经网络分类器,它接收大量图像(每张约 1-3 GB),对它们进行修补,然后将这些修补程序单独传递给网络。训练进行得很慢真的,所以我对它进行了基准测试,发现将补丁从一张图像加载到内存中需要大约 50 秒(使用 Openslide library),并且只有大约 0.5 秒才能通过他们通过模型。

但是,我正在开发一台具有 1.5Tb 内存的超级计算机,其中仅使用了 ~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)没有解决 Python 脚本之间共享内存在不同 Docker 容器中的集成问题。

我的问题:

  • 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

【问题讨论】:

  • 我怀疑您的容器化设置的问题是您的 docker 容器位于不同的网络中,并且无法通过 127.0.0.1 相互通信。您可以尝试以--network host 开头,这可能会有所帮助。
  • 感谢您的评论 - 这很有帮助,它让我走得更远。该程序不是在client.py 中的manager.connect() 上获得ConnectionRefusedError,而是将其变为image_patches = patch_dict.get(key),但引发this error
  • @JacobStern,您使用的是网络而不是ipc。而不是使用--ipc=container:cancer-1 使用--network=container:cancer-1 然后尝试
  • 这是有道理的。所以在后台,服务器进程使用通过网络通信的套接字进行通信,需要 docker 容器之间的网络通信?
  • 根据 [这篇文章] (dzone.com/articles/docker-in-action-the-shared-memory-namespace) 和其他一些人的说法,共享内存听起来像是要走的路,因为网络/管道速度几乎没有内存速度那么快。对吗?

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


【解决方案1】:

我建议你尝试使用tmpfs

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

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

  • 无需修改当前代码 - 数据集的结构保持不变
  • 无需额外工作即可创建共享数据集 - 只需将 cp 数据集放入 tmpfs
  • 通用接口 - 作为一个文件系统,您可以轻松地将内存上的数据集与系统中不一定用 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

【讨论】:

  • 这是迄今为止最简单的;但文档建议 tmpfs 不能用于在容器之间共享内存:docs.docker.com/storage/tmpfs - 但这是主机上的 tmpfs,由容器作为常规卷安装,对吗?
  • 谢谢 - 我按照你的描述试过了,但我没有看到任何加速。从/tumor/my_file.tif 访问一个文件的时间:4.4349s。从/mnt/mytmpfs/tumor/my_file.tif 访问一个文件的时间:4.6474 秒。知道为什么会这样吗?
  • @NinoWalker 是的,tmpfs 在主机上
  • @JacomStern 您可以将其与主机上的访问时间进行比较吗?我们在谈论什么样的访问?您是否尝试仅读取文件?此外,tmpfs 实现了交换,这可能会导致类似磁盘的速度。您可以尝试使用 ramfs(更低级别,无需交换)来排除这种情况。使用命令是一样的,把tmpfs换成ramfs就好了。
  • 我在本地机器上查了一下,tmpfs明显比ramfs慢,推荐你试试。如果可行,我将相应地编辑我的答案:)
【解决方案2】:

我认为shared memorymmap 解决方案是合适的。

共享内存:

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

mmap:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-28
    • 2015-05-24
    • 1970-01-01
    • 2014-10-08
    • 2014-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多