Использование файлов для IPC общей памяти, является ли отображение памяти требованием?

голоса
19

Есть несколько проектов, которые используют MappedByteBuffers, возвращаемые функцией FileChannel.map() Java, как способ иметь IPC общей памяти между JVM на одном и том же хосте (см. Chronicle Queue, Aeron IPC, и т.д.). Насколько я могу судить, этот api просто сидит на вершине вызова mmap. Тем не менее, реализация Java не позволяет анонимное (без файловой поддержки) отображение.

Мой вопрос в том, на Java (1.8) и Linux (3.10), действительно ли MappedByteBuffers необходимы для реализации IPC с общей памятью, или любой доступ к общему файлу обеспечит такую же функциональность? (Этот вопрос не касается последствий использования MappedByteBuffer для производительности или нет)

Вот мое понимание:

  1. Когда Linux загружает файл с диска, он копирует его содержимое на страницы в памяти. Эта область памяти называется кэшем страниц. Насколько я могу судить, он делает это независимо от того, какой метод Java (FileInputStream.read(), RandomAccessFile.read(), FileChannel.read(), FileChannel.map()) или нативный метод используется для чтения файла (обсеянный свободным и отслеживающий значение кэша).
  2. Если другой процесс пытается загрузить тот же самый файл (в то время как он все еще находится в кэше), ядро обнаруживает это и не нуждается в перезагрузке файла. Если кэш страниц переполнится, страницы будут вытеснены - грязные будут выписаны обратно на диск. (Страницы также будут записываться обратно, если есть явный флеш на диск, и периодически, с потоком ядра).
  3. Наличие (большого) файла уже в кэше - это значительный прирост производительности, гораздо больший, чем различия, на основе которых мы используем методы Java для открытия/чтения этого файла.
  4. Программа на C, вызывающая системный вызов mmap, может выполнять сопоставление ANONYMOUS, которое, по сути, распределяет страницы в кэше, которые не подкреплены реальным файлом (так что нет необходимости выпускать реальную запись на диск), но Java, похоже, не предлагает этого (может ли сопоставление файла в tmpfs сделать то же самое?)
  5. Если файл загружается с помощью вызова mmap-системы (C) или через FileChannel.map() (Java), то, по сути, страницы файла (в кэше) загружаются непосредственно в адресное пространство процесса. Используя другие методы для открытия файла, файл загружается в страницы не в адресном пространстве процесса, а затем различные методы чтения/записи этого файла копируют некоторые байты из/в эти страницы в буфер в адресном пространстве процесса. Есть очевидное преимущество в производительности, позволяющее избежать такой копии, но мой вопрос не касается производительности.

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

Так что, пожалуйста, дайте мне знать, где мое понимание.

Спасибо.

Задан 22/05/2020 в 21:20
источник пользователем
На других языках...                            


2 ответов

голоса
0

Стоит упомянуть три момента: производительность и одновременные изменения, а также использование памяти.

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

Рассмотрим возможность изменения N-го байта: с помощью mmap buffer[N] = buffer[N] + 1, а с помощью файлового доступа необходимо (как минимум) 4 проверки на ошибки системных вызовов:

   seek() + error check
   read() + error check
   update value
   seek() + error check
   write + error check

Это правда, что количество реальных IO (на диск), скорее всего, будет одинаковым.

Второй момент стоит отметить одновременный доступ. При использовании файлового ввода-вывода вы должны беспокоиться о потенциальном параллельном доступе. Вам нужно будет произвести явное блокирование (до чтения) и разблокирование (после записи), чтобы предотвратить два процесса, которые некорректно обращаются к значению одновременно. При использовании разделяемой памяти, атомарные операции могут устранить необходимость в дополнительной блокировке.

Третий момент - фактическое использование памяти. В случаях, когда размер общих объектов значителен, использование общей памяти может позволить большому количеству процессов получить доступ к данным без выделения дополнительной памяти. Если системы, ограниченные памятью, или системы, которым необходимо обеспечить производительность в реальном времени, это может быть единственным способом доступа к данным.

Ответил 29/05/2020 в 10:35
источник пользователем

голоса
0

Мой вопрос в том, на Java (1.8) и Linux (3.10), действительно ли MappedByteBuffers необходимы для реализации IPC с разделяемой памятью, или любой доступ к общему файлу обеспечит такую же функциональность?

Это зависит от того, почему вы хотите реализовать IPC разделяемой памяти.

Вы можете четко реализовать IPC без разделяемой памяти; например, через сокеты. Таким образом, если Вы делаете это не из соображений производительности, то IPC с разделяемой памятью не нужно делать вообще!

Поэтому производительность должна быть в основе любого обсуждения.

Доступ с использованием файлов через классический интерфейс Java io или nio API не обеспечивает функциональности или производительности общей памяти.

Основное различие между обычным файловым вводом/выводом или вводом/выводом с сокета и IPC с общей памятью заключается в том, что первый требует от приложений явного выполнения readи системного writeмасштабирования для отправки и получения сообщений. Это влечет за собой дополнительные syscalls и копирование данных ядром. Более того, если существует несколько потоков, вам нужен либо отдельный "канал" между каждой парой потоков, либо что-то для мультиплексирования нескольких "разговоров" по общему каналу. Последнее может привести к тому, что общий канал станет параллельным узким местом.

Обратите внимание, что эти накладные расходы ортогональны к кэшу страниц Linux.

В отличие от этого, в IPC, реализованном с использованием разделяемой памяти, нет readи writeсистемных масштабируемых файлов, и нет дополнительного шага копирования. Каждый "канал" может просто использовать отдельную область отображаемого буфера. Поток в одном процессе записывает данные в общую память, и это практически сразу видно второму процессу.

Оговорка заключается в том, что процессы должны 1) синхронизироваться, и 2) реализовывать барьеры памяти, чтобы читатель не видел несвежих данных. Но и то и другое может быть реализовано без сизкалов.

В процессе стирки IPC с общей памятью, использующей файлы mapped memory >>, работает<< быстрее, чем с обычными файлами или сокетами, и поэтому люди делают это.


Вы также неявно спрашиваете, можно ли реализовать IPC с общей памятью без файлов с отображением в памяти.

  • Практическим способом было бы создание файла, отображенного на карте памяти, для файла, который живет в файловой системе только на основе памяти; например, "tmpfs" в Linux.

    Технически, это все еще файл, отображенный на карте памяти. Тем не менее, вы не подвергаетесь накладным расходам при промывке данных на диск и избегаете потенциальной угрозы безопасности, связанной с тем, что личные IPC-данные окажутся на диске.

  • Теоретически можно было бы реализовать общий сегмент между двумя процессами, сделав следующее:

    • В родительском процессе используйте mmap для создания сегмента с MAP_ANONYMOUS | MAP_SHARED.
    • Детские процессы вилки. В конце концов, все они делят сегмент между собой и родительским процессом.

    Однако реализация этого для Java-процесса будет ... сложной задачей. AFAIK, Java не поддерживает это.

Ссылка:

Ответил 31/05/2020 в 06:17
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more