IPC разделяет память между скриптами Python в отдельных контейнерах Docker - PullRequest
13 голосов
/ 05 июля 2019

Проблема

Я написал классификатор нейронной сети, который принимает массивные изображения (~ 1-3 ГБ за штуку), исправляет их и передает патчи по сети индивидуально. Обучение шло действительно медленно, поэтому я проверил его и обнаружил, что для загрузки патчей из одного изображения в память требуется ~ 50 с (используя библиотеку Openslide ), и только ~. 5 секунд, чтобы передать их через модель.

Тем не менее, я работаю на суперкомпьютере с 1,5 ТБ ОЗУ, из которых используется только ~ 26 ГБ. Набор данных составляет ~ 500 Гб. Я думаю, что если бы мы могли загрузить весь набор данных в память, это значительно ускорило бы обучение. Но я работаю с исследовательской группой, и мы проводим эксперименты на нескольких скриптах Python. Поэтому в идеале я хотел бы загрузить весь набор данных в память одним сценарием и иметь доступ к нему для всех сценариев.

Подробнее:

  • Мы проводим наши индивидуальные эксперименты в отдельных контейнерах Docker (на одной машине), поэтому набор данных должен быть доступен для нескольких контейнеров.
  • Набор данных - это Camelyon16 Dataset ; изображения хранятся в формате .tif.
  • Нам просто нужно прочитать изображения, не нужно писать.
  • За один раз нам нужен только небольшой доступ к набору данных.

Возможные решения

Я нашел много сообщений о том, как делиться объектами Python или необработанными данными в памяти между несколькими скриптами Python:

Обмен данными Python между скриптами

Серверные процессы с SyncManager и BaseManager в многопроцессорном модуле | Пример 1 | Пример 2 | Документы - Серверные процессы | Документы - SyncManager

  • Положительные стороны: могут использоваться общими процессами на разных компьютерах в сети (могут ли они использоваться несколькими контейнерами?)
  • Возможная проблема: медленнее, чем использование общей памяти, согласно документации. Если мы разделяем память между несколькими контейнерами, используя клиент / сервер, будет ли это быстрее, чем все сценарии, читающие с диска?
  • Возможная проблема: в соответствии с этим ответом , объект Manager выбирает объекты перед отправкой, что может привести к замедлению.

mmap module | Docs

Pyro4 (клиент-сервер для объектов Python) | Docs

Модуль 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 даже лучшим способом совместного использования памяти между контейнерами докеров?)
  • 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

Ответы [ 2 ]

4 голосов
/ 13 июля 2019

Я рекомендую вам попробовать tmpfs .

Это функция Linux, позволяющая вам создать виртуальную файловую систему, которая хранится в оперативной памяти. Это обеспечивает очень быстрый доступ к файлу и для настройки требуется всего одна команда bash.

Помимо того, что он очень быстрый и простой, в вашем случае он имеет много преимуществ:

  • Не нужно трогать текущий код - структура набора данных остается неизменной
  • Никаких дополнительных действий для создания общего набора данных - просто cp набор данных в tmpfs
  • Общий интерфейс - будучи файловой системой, вы можете легко интегрировать набор данных в оперативной памяти с другим компонентом в вашей системе, который не обязательно написан на python. Например, это было бы легко использовать внутри ваших контейнеров, просто передав в них каталог монтирования.
  • Подходит для других сред - если ваш код должен будет работать на другом сервере, tmpfs может адаптировать и поменять страницы на жестком диске. Если вам придется запускать это на сервере без свободной оперативной памяти, вы можете просто хранить все свои файлы на жестком диске с нормальной файловой системой и вообще не трогать ваш код.

Шаги для использования:

  1. Создать tmpfs - sudo mount -t tmpfs -o size=600G tmpfs /mnt/mytmpfs
  2. Копировать набор данных - cp -r dataset /mnt/mytmpfs
  3. Изменить все ссылки из текущего набора данных на новый набор данных
  4. Наслаждайтесь


Edit:

ramfs может быть быстрее, чем tmpfs в некоторых случаях, так как он не реализует перестановку страниц. Чтобы использовать его, просто замените tmpfs на ramfs в инструкциях выше.

0 голосов
/ 06 июля 2019

Я думаю, что shared memory или mmap решение является правильным.

общая память:

Первое чтение набора данных в памяти в процессе сервера.Для python просто используйте оболочку multiprocessing для создания объекта в разделяемой памяти между процессами, например: multiprocessing.Value или multiprocessing.Array , затем создайте Process и передайте общий объект какарг.

mmap:

Сохранение набора данных в файле на хосте.Затем каждый контейнер монтирует файл в контейнер.Если один контейнер откроет файл и отобразит файл в его виртуальную память, другому контейнеру не нужно будет считывать файл с диска в память при открытии файла, поскольку файл уже находится в физической памяти.

PS Яне уверен, как реализация cpython большая общая память между процессами, вероятно, общая память cpython использовать mmap внутренняя.

...