python - IPC 在单独的 Docker 容器中跨 Python 脚本共享内存

标签 python docker ipc python-multiprocessing shared-memory

问题

我编写了一个神经网络分类器,它接收大量图像(每张约 1-3 GB),将它们拼凑起来,然后将这些拼块单独通过网络。训练进行得非常缓慢,所以我对它进行了基准测试,发现将一个图像中的补丁加载到内存中需要大约 50 秒(使用 Openslide library),而将它们传递到模型中只需要大约 0.5 秒。

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

更多细节:

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

  • 可能的解决方案

    我发现了很多关于如何跨多个 Python 脚本共享内存中的 Python 对象或原始数据的帖子:

    跨脚本共享 Python 数据

    多处理模块中带有 SyncManager 和 BaseManager 的服务器进程 | Example 1 | Example 2 | Docs - Server Processes | Docs - SyncManagers
  • 优点:可以通过网络由不同计算机上的进程共享(可以由多个容器共享吗?)
  • 可能的问题:根据文档,比使用共享内存慢。如果我们使用客户端/服务器在多个容器之间共享内存,这会比从磁盘读取的所有脚本更快吗?
  • 可能的问题:根据 this answer , Manager object 在发送对象之前先腌制对象,这可能会减慢速度。

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

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

    sysv_ipc Python 模块。 This demo看起来很有希望。
  • 可能的问题:也许只是一个 lower level exposure内置可用的东西multi-processing模块?

  • 我还找到了 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
    

    最佳答案

    我建议您尝试使用 tmpfs .

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

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

  • 无需接触当前代码 - 数据集的结构保持不变
  • 无需额外工作即可创建共享数据集 - 只需 cp将数据集放入 tmpfs
  • 通用接口(interface) - 作为文件系统,您可以轻松地将 RAM 上的数据集与系统中不一定用 Python 编写的其他组件集成。例如,在你的容器中使用它会很容易,只需将挂载的目录传递给它们。
  • 将适合其他环境 - 如果您的代码必须在不同的服务器上运行,tmpfs可以适应和交换页面到硬盘驱动器。如果您必须在没有可用 RAM 的服务器上运行它,您可以将所有文件都放在带有普通文件系统的硬盘驱动器上,而根本不接触您的代码。

  • 使用步骤:
  • 创建一个 tmpfs - sudo mount -t tmpfs -o size=600G tmpfs /mnt/mytmpfs
  • 复制数据集 - cp -r dataset /mnt/mytmpfs
  • 将当前数据集的所有引用更改为新数据集
  • 享受

  • 编辑:
    ramfs可能比 tmpfs 快在某些情况下,因为它不实现页面交换。要使用它只需替换 tmpfsramfs在上面的说明中。

    关于python - IPC 在单独的 Docker 容器中跨 Python 脚本共享内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56908476/

    相关文章:

    python - 插入字典会导致KeyError吗?

    Python 3 并发.futures : How to add back failed futures to ThreadPoolExecutor?

    docker - Juypterlab的Docker容器

    delphi - 网站与 win32 可执行文件通信的标准方法是什么?

    linux - IPC 与 imsg? OpenBSD 和 Linux 兼容性?

    python - 具有装饰功能的多处理池给了我 "object has no attribute"

    Python 多维列表 - IndexError : list index out of range

    php - C++ 和 PHP 中的命名管道

    python - 在没有特权模式的Docker环境中操纵Docker中容器之间的网络流量?

    docker - 无法删除 Kubernetes 中的所有 pod - 清除/重启 Kubernetes