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

Есть несколько проектов, которые используют 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 и страница кэш-памяти. 

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

Спасибо.

 

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

Вы правы в оценке, что 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 (на диск), скорее всего, будет одинаковым.

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

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

 

> 
> Мой вопрос в том, на 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 не поддерживает это.

Ссылка:

* [Каково назначение флага MAP\_ANONYMOUS в системном вызове mmap?](https://stackoverflow.com/questions/34042915/what-is-the-purpose-of-map-anonymous-flag-in-mmap-system-call)