【发布时间】: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 answer,
Manager对象在发送对象之前对其进行腌制,这可能会减慢速度。
- 可能的问题:
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 问题(1、2、3、4)没有解决 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