Ist die Verwendung von Dateien für die IPC mit gemeinsam genutztem Speicher eine Anforderung?

stimmen
19

Es gibt einige Projekte, die die MappedByteBuffer, die von Javas FileChannel.map() zurückgegeben werden, als einen Weg benutzen, um eine IPC mit gemeinsamem Speicher zwischen JVMs auf demselben Host zu haben (siehe Chronicle Queue, Aeron IPC, etc.). Soweit ich das beurteilen kann, sitzt diese api einfach über dem mmap-Aufruf. Die Java-Implementierung erlaubt jedoch keine anonymen (nicht durch Dateien gesicherten) Mappings.

Meine Frage ist, ob auf Java (1.8) und Linux (3.10) MappedByteBuffer wirklich notwendig sind, um eine IPC mit gemeinsamem Speicher zu implementieren, oder würde jeder Zugriff auf eine gemeinsame Datei die gleiche Funktionalität bieten? (Diese Frage bezieht sich nicht auf die Leistungsimplikation der Verwendung eines MappedByteBuffer oder nicht)

Hier ist mein Verständnis:

  1. Wenn Linux eine Datei von der Festplatte lädt, kopiert es den Inhalt dieser Datei auf Seiten im Speicher. Dieser Bereich des Speichers wird als Seiten-Cache bezeichnet. Soweit ich das beurteilen kann, geschieht dies unabhängig davon, welche Java-Methode (FileInputStream.read(), RandomAccessFile.read(), FileChannel.read(), FileChannel.map()) oder native Methode zum Lesen der Datei verwendet wird (mit free und Überwachung des Cache-Wertes).
  2. Wenn ein anderer Prozess versucht, die gleiche Datei zu laden (während sie sich noch im Cache befindet), erkennt der Kernel dies und braucht die Datei nicht neu zu laden. Wenn der Seiten-Cache voll wird, werden Seiten verdrängt - schmutzige Seiten werden auf die Platte zurückgeschrieben. (Seiten werden auch dann wieder herausgeschrieben, wenn es einen expliziten Flush auf die Platte gibt, und zwar periodisch mit einem Kernel-Thread).
  3. Wenn sich eine (große) Datei bereits im Cache befindet, ist das eine erhebliche Leistungssteigerung, viel mehr noch als die Unterschiede, die darauf beruhen, welche Java-Methoden wir zum Öffnen/Lesen dieser Datei verwenden.
  4. Ein C-Programm, das den mmap-Systemaufruf aufruft, kann ein ANONYMOUS-Mapping durchführen, das im Wesentlichen Seiten im Cache zuweist, die nicht durch eine tatsächliche Datei gesichert sind (es besteht also keine Notwendigkeit, tatsächliche Schreibzugriffe auf die Platte durchzuführen), aber Java scheint das nicht anzubieten (würde das Mapping einer Datei in tmpfs dasselbe bewirken?)
  5. Wenn eine Datei über den mmap-Systemaufruf (C) oder über FileChannel.map() (Java) geladen wird, werden im Wesentlichen die Seiten der Datei (im Cache) direkt in den Adressraum des Prozesses geladen. Mit anderen Methoden zum Öffnen einer Datei wird die Datei in Seiten geladen, die nicht im Adressraum des Prozesses liegen, und dann kopieren die verschiedenen Methoden zum Lesen/Schreiben dieser Datei einige Bytes von/zu diesen Seiten in einen Puffer im Adressraum des Prozesses. Es gibt einen offensichtlichen Leistungsvorteil, diese Kopie zu vermeiden, aber meine Frage bezieht sich nicht auf die Leistung.

Zusammenfassend kann ich also sagen, wenn ich es richtig verstehe, dass Mapping zwar einen Leistungsvorteil bietet, aber keine Shared-Memory-Funktionalität, die wir nicht schon allein aus der Natur von Linux und dem Seiten-Cache erhalten.

Lassen Sie mich bitte wissen, woran es mit meinem Verständnis scheitert.

Danke.

Veröffentlicht am 22/05/2020 um 21:20
quelle vom benutzer
In anderen Sprachen...                            


2 antworten

stimmen
0

Erwähnenswert sind drei Punkte: Leistung und gleichzeitige Änderungen sowie Speichernutzung.

Sie haben Recht mit der Einschätzung, dass MMAP-basierter Zugriff in der Regel einen Leistungsvorteil gegenüber dateibasiertem IO bietet. Insbesondere ist der Leistungsvorteil signifikant, wenn der Code viele kleine IOs am artbiträren Punkt der Datei ausführt.

ziehen Sie in Betracht, das N-te Byte zu ändern: Bei mmap buffer[N] = buffer[N] + 1und beim dateibasierten Zugriff benötigen Sie (mindestens) 4 Systemaufrufe zur Fehlerprüfung:

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

Es stimmt, dass die Anzahl der tatsächlichen IO (auf der Platte) höchstwahrscheinlich gleich ist.

Der zweite erwähnenswerte Punkt ist der gleichzeitige Zugriff. Bei dateibasierter IO müssen Sie sich um einen möglichen konkurrierenden Zugriff kümmern. Sie müssen explizit sperren (vor dem Lesen) und entsperren (nach dem Schreiben), um zu verhindern, dass zwei Prozesse fälschlicherweise gleichzeitig auf den Wert zugreifen. Bei Shared Memory können atomare Operationen die Notwendigkeit einer zusätzlichen Sperre beseitigen.

Der dritte Punkt ist die tatsächliche Speichernutzung. In Fällen, in denen die Größe der gemeinsam genutzten Objekte signifikant ist, kann die Verwendung von gemeinsamem Speicher einer großen Anzahl von Prozessen den Zugriff auf die Daten ermöglichen, ohne dass zusätzlicher Speicher zugewiesen wird. Wenn Systeme, die durch den Speicher eingeschränkt sind, oder Systeme, die Echtzeit-Performance bieten müssen, könnte dies die einzige Möglichkeit sein, auf die Daten zuzugreifen.

Beantwortet am 29/05/2020 um 10:35
quelle vom benutzer

stimmen
0

Meine Frage ist, ob auf Java (1.8) und Linux (3.10) MappedByteBuffer wirklich notwendig sind, um eine IPC mit gemeinsamem Speicher zu implementieren, oder würde jeder Zugriff auf eine gemeinsame Datei die gleiche Funktionalität bieten?

Es hängt davon ab, warum Sie Shared Memory IPC implementieren wollen.

Sie können IPC eindeutig ohne Shared Memory implementieren; z.B. über Sockets. Wenn Sie es also aus Performance-Gründen nicht tun, ist es gar nicht notwendig, die IPC mit Shared Memory durchzuführen!

Die Leistung muss also die Grundlage jeder Diskussion sein.

Der Zugriff über Dateien über die klassischen io- oder nio-APIs von Java bietet keine Shared-Memory-Funktionalität oder -Leistung.

Der Hauptunterschied zwischen regulärem Datei-I/O oder Socket-I/O und Shared Memory IPC besteht darin, dass Ersteres von den Anwendungen explizit das explizite Senden readund writeEmpfangen von Nachrichten erfordert. Dies bringt zusätzliche Syscalls mit sich und führt dazu, dass der Kernel Daten kopiert. Wenn es außerdem mehrere Threads gibt, benötigen Sie entweder einen separaten "Kanal" zwischen jedem Thread-Paar oder etwas zum Multiplexen mehrerer "Konversationen" über einen gemeinsamen Kanal. Letzteres kann dazu führen, dass der gemeinsam genutzte Kanal zu einem Engpass für die Gleichzeitigkeit wird.

Beachten Sie, dass diese Overheads orthogonal zum Linux-Seiten-Cache sind.

Im Gegensatz dazu gibt es bei der IPC, die mit Shared Memory implementiert wurde, keine readund writeSyscalls und keinen zusätzlichen Kopierschritt. Jeder "Kanal" kann einfach einen separaten Bereich des gemappten Puffers verwenden. Ein Thread in einem Prozess schreibt Daten in den gemeinsamen Speicher und diese sind für den zweiten Prozess fast sofort sichtbar.

Der Vorbehalt ist, dass die Prozesse 1) sich synchronisieren und 2) Speicherbarrieren implementieren müssen, um sicherzustellen, dass der Leser keine veralteten Daten sieht. Aber beide können ohne Syscalls implementiert werden.

Beim Waschen ist die IPC mit gemeinsam genutztem Speicher unter Verwendung von Memory-Mapped-Dateien >>ist<< schneller als unter Verwendung herkömmlicher Dateien oder Sockets, und das ist der Grund, warum Menschen dies tun.


Sie haben auch implizit gefragt, ob die Shared-Memory IPC ohne Memory-Mapping-Dateien implementiert werden kann.

  • Ein praktischer Weg wäre es, eine memory-mapped Datei für eine Datei zu erstellen, die in einem Nur-Speicher-Dateisystem lebt; z.B. ein "tmpfs" in Linux.

    Technisch gesehen handelt es sich dabei immer noch um eine Memory-mapped-Datei. Es entstehen Ihnen jedoch keine Overhead-Kosten für das Flushen von Daten auf die Festplatte, und Sie vermeiden die potentiellen Sicherheitsbedenken privater IPC-Daten, die auf der Festplatte landen.

  • Sie könnten theoretisch ein gemeinsames Segment zwischen zwei Prozessen implementieren, indem Sie wie folgt vorgehen:

    • Verwenden Sie im Elternprozess mmap, um ein Segment mit MAP_ANONYMOUS | MAP_SHAREDzu erstellen.
    • Gabelung von Child-Prozessen. Am Ende teilen sich alle das Segment untereinander und den übergeordneten Prozess.

    Dies für einen Java-Prozess zu implementieren, wäre jedoch ... eine Herausforderung. AFAIK, Java unterstützt dies nicht.

Referenz zu erstellen:

Beantwortet am 31/05/2020 um 06:17
quelle vom benutzer

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