Общая память IPC для скриптов Python в отдельных контейнерах Docker
Эта проблема
Я написал классификатор нейронной сети, который принимает большие изображения (~ 1-3 ГБ за штуку), исправляет их и передает патчи по сети индивидуально. Обучение шло очень медленно, поэтому я провел сравнительный анализ и обнаружил, что для загрузки патчей из одного изображения в память (с использованием библиотеки Openslide) требуется ~ 50 с, а для прохождения их через модель - ~ 0,5 с.
Тем не менее, я работаю над суперкомпьютером с 1,5 ТБ ОЗУ, из которых используется только ~ 26 ГБ. Набор данных составляет ~ 500 Гб. Я думаю, что если бы мы могли загрузить весь набор данных в память, это значительно ускорило бы обучение. Но я работаю с исследовательской группой, и мы проводим эксперименты на нескольких скриптах Python. Поэтому в идеале я хотел бы загрузить весь набор данных в память одним сценарием и иметь доступ к нему для всех сценариев.
Больше деталей:
- Мы проводим наши индивидуальные эксперименты в отдельных контейнерах Docker (на одной машине), поэтому набор данных должен быть доступен для нескольких контейнеров.
- Набор данных - это набор данных Camelyon16; изображения хранятся в формате
.tif
. - Нам просто нужно прочитать изображения, не нужно писать.
- Нам нужно только получить доступ к небольшим частям набора данных одновременно.
Возможные решения
Я нашел много постов о том, как делиться объектами Python или необработанными данными в памяти между несколькими скриптами Python:
Обмен данными Python между скриптами
Серверные процессы с SyncManager и BaseManager в многопроцессорном модуле | Пример 1 | Пример 2 | Документы - Серверные процессы | Документы - SyncManager
- Положительные стороны: могут совместно использоваться процессами на разных компьютерах по сети (могут ли они использоваться несколькими контейнерами?)
- Возможная проблема: медленнее, чем использование общей памяти, в соответствии с документацией. Если мы разделяем память между несколькими контейнерами, используя клиент/сервер, будет ли это быстрее, чем все сценарии, читающие с диска?
- Возможная проблема: в соответствии с этим ответом объект
Manager
выбирает объекты перед отправкой, что может привести к замедлению работы.
модуль mmap | Документы
- Возможная проблема:
mmap
отображает файл в виртуальную память, а не в физическую память - он создает временный файл. - Возможная проблема: поскольку мы используем только небольшую часть набора данных за один раз, виртуальная память помещает весь набор данных на диск, мы сталкиваемся с серьезными проблемами и ошибками программы.
Pyro4 (клиент-сервер для объектов Python) | Документы
Модуль sysv_ipc для Python. Это демо выглядит многообещающе.
Я также нашел этот список опций для IPC/сетей в Python.
Некоторые обсуждают настройки сервера-клиента, некоторые обсуждают сериализацию/десериализацию, которая, боюсь, займет больше времени, чем просто чтение с диска. Ни один из ответов, которые я нашел, не отвечает на мой вопрос о том, приведут ли они к повышению производительности ввода-вывода.
Совместное использование памяти через контейнеры Docker
Нам нужно не только разделять объекты/память Python между скриптами; нам нужно поделиться ими через контейнеры Docker.
Docker документация объясняет --ipc
флаг очень хорошо. Что имеет смысл для меня по документации работает:
docker run -d --ipc=shareable data-server
docker run -d --ipc=container:data-server data-client
Но когда я запускаю свой клиент и сервер в отдельных контейнерах с подключением --ipc
настроенным, как описано выше, они не могут общаться друг с другом. Вопросы, которые я прочитал (1, 2, 3, 4), не касаются интеграции разделяемой памяти между скриптами Python в отдельных контейнерах Docker.
Мои вопросы:
- 1: Предоставит ли какой-либо из них более быстрый доступ, чем чтение с диска? Разумно ли даже думать, что совместное использование данных в памяти между процессами/контейнерами повысит производительность?
- 2: Какое решение будет наиболее подходящим для совместного использования данных в памяти между несколькими контейнерами докеров?
- 3: Как интегрировать решения для совместного использования памяти из Python с
docker run --ipc=<mode>
? (Является ли общее пространство имен IPC даже лучшим способом разделения памяти между контейнерами Docker?) - 4: есть ли лучшее решение, чем это, чтобы решить нашу проблему больших накладных расходов ввода-вывода?
Минимальный рабочий пример - Обновлено. Не требует никаких внешних зависимостей!
Это мой наивный подход к разделению памяти между скриптами 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
Ответы
Ответ 1
Я рекомендую вам попробовать использовать tmpfs.
Это функция Linux, позволяющая вам создать виртуальную файловую систему, которая хранится в оперативной памяти. Это обеспечивает очень быстрый доступ к файлу и требует всего одну команду bash для настройки.
Помимо того, что он очень быстрый и понятный, он имеет много преимуществ в вашем случае:
- Не нужно трогать текущий код - структура набора данных остается неизменной
- Никакой дополнительной работы по созданию общего набора данных - просто
cp
набор данных в tmpfs
- Общий интерфейс - будучи файловой системой, вы можете легко интегрировать набор данных -r AM с другим компонентом вашей системы, который не обязательно написан на python. Например, это было бы легко использовать внутри ваших контейнеров, просто передав в них каталог монтирования.
- Подходит для других сред - если ваш код должен будет работать на другом сервере,
tmpfs
может адаптировать и поменять страницы на жестком диске. Если вам придется запускать это на сервере без свободной оперативной памяти, вы можете просто хранить все свои файлы на жестком диске с нормальной файловой системой и вообще не трогать ваш код.
Шаги для использования:
- Создайте tmpfs -
sudo mount -t tmpfs -o size=600G tmpfs/mnt/mytmpfs
- Копировать набор данных -
cp -r dataset/mnt/mytmpfs
- Изменить все ссылки из текущего набора данных на новый набор данных
- наслаждаться
Редактировать: ramfs
может быть быстрее чем tmpfs
в некоторых случаях, так как он не реализует перестановку страниц. Чтобы использовать его, просто замените tmpfs
на ramfs
в инструкциях выше.
Ответ 2
Я думаю, что shared memory
или решение mmap
является правильным.
Общая память:
Сначала прочитайте набор данных в памяти в процессе сервера. Для python просто используйте multiprocessing
оболочку для создания объекта в разделяемой памяти между процессами, например: multiprocessing.Value или multiprocessing.Array, затем создайте Process и передайте общий объект как аргументы.
ММАП:
Храните набор данных в файле на хосте. Затем каждый контейнер монтирует файл в контейнер. Если один контейнер откроет файл и отобразит файл в его виртуальную память, другому контейнеру не потребуется считывать файл с диска в память при открытии файла, поскольку файл уже находится в физической памяти.
PS Я не уверен, как реализация cpython большая общая память между процессами, вероятно, общая память cpython использует внутреннюю mmap
.