schnellste Methode (niedrige Latenz) für die prozessübergreifende Kommunikation zwischen Java und C / C ++

100

Ich habe eine Java-App, die über den TCP-Socket eine Verbindung zu einem in C / C ++ entwickelten "Server" herstellt.

App und Server laufen auf demselben Computer, einer Solaris-Box (wir erwägen jedoch eine eventuelle Migration auf Linux). Die Art der ausgetauschten Daten sind einfache Nachrichten (Login, Login ACK, dann Client fragt nach etwas, Server antwortet). Jede Nachricht ist ungefähr 300 Bytes lang.

Derzeit verwenden wir Sockets und alles ist in Ordnung. Ich suche jedoch nach einer schnelleren Möglichkeit, Daten mithilfe von IPC-Methoden auszutauschen (geringere Latenz).

Ich habe im Internet recherchiert und Hinweise auf folgende Technologien gefunden:

  • geteilte Erinnerung
  • Rohre
  • Warteschlangen
  • sowie was als DMA (Direct Memory Access) bezeichnet wird

Aber ich konnte keine richtige Analyse ihrer jeweiligen Leistungen finden und auch nicht, wie man sie sowohl in JAVA als auch in C / C ++ implementiert (damit sie miteinander sprechen können), außer vielleicht Pipes, die ich mir vorstellen könnte.

Kann jemand die Leistung und Machbarkeit jeder Methode in diesem Zusammenhang kommentieren? Gibt es einen Zeiger / Link zu nützlichen Implementierungsinformationen?


EDIT / UPDATE

Nach dem Kommentar und den Antworten, die ich hier erhalten habe, habe ich Informationen zu Unix Domain Sockets gefunden, die scheinbar nur über Pipes erstellt wurden und mir den gesamten TCP-Stack ersparen würden. Es ist plattformspezifisch, daher plane ich, es mit JNI oder entweder Juds oder Junixsocket zu testen .

Die nächsten möglichen Schritte wären die direkte Implementierung von Pipes und dann der gemeinsame Speicher, obwohl ich vor der zusätzlichen Komplexität gewarnt wurde ...


danke für Ihre Hilfe

Bastien
quelle
7
Es könnte in Ihrem Fall übertrieben sein,
jfs
Das ist interessant, aber die Idee wäre, zuerst "generische" (wie in Betriebssystemen oder Sprachen bereitgestellte) Methoden zu verwenden. Deshalb habe ich Warteschlangen und gemeinsam genutzten Speicher erwähnt.
Bastien
2
Siehe auch stackoverflow.com/questions/904492
MSalters
Vergessen Sie nicht zugeordnete Dateien oder nur UDP.
10
UDP langsamer als TCP ??? hmmm ... Beweis bitte
Boppity Bop

Antworten:

103

Gerade getestete Latenz von Java auf meinem Corei5 2.8GHz, nur Einzelbyte senden / empfangen, 2 Java-Prozesse wurden gerade erzeugt, ohne bestimmte CPU-Kerne mit Task-Set zuzuweisen:

TCP         - 25 microseconds
Named pipes - 15 microseconds

Geben Sie nun explizit Kernmasken an , z. B. Task-Set 1 Java Srv oder Task-Set 2 Java Cli :

TCP, same cores:                      30 microseconds
TCP, explicit different cores:        22 microseconds
Named pipes, same core:               4-5 microseconds !!!!
Named pipes, taskset different cores: 7-8 microseconds !!!!

so

TCP overhead is visible
scheduling overhead (or core caches?) is also the culprit

Gleichzeitig dauert Thread.sleep (0) (was, wie Strace zeigt, dazu führt, dass ein einzelner Linux-Kernelaufruf sched_yield () ausgeführt wird) 0,3 Mikrosekunden - so genannte Pipes, die für einen einzelnen Kern geplant sind, haben immer noch viel Overhead

Einige Messungen des gemeinsam genutzten Speichers: 14. September 2009 - Solace Systems gab heute bekannt, dass seine Unified Messaging Platform-API mithilfe eines gemeinsam genutzten Speichertransports eine durchschnittliche Latenz von weniger als 700 Nanosekunden erreichen kann. http://solacesystems.com/news/fastest-ipc-messaging/

PS - am nächsten Tag versucht, gemeinsam genutzten Speicher in Form von Dateien mit Speicherzuordnung zu verwenden. Wenn das Warten auf viel zu tun akzeptabel ist, können wir die Latenz auf 0,3 Mikrosekunden reduzieren, um ein einzelnes Byte mit folgendem Code zu übergeben:

MappedByteBuffer mem =
  new RandomAccessFile("/tmp/mapped.txt", "rw").getChannel()
  .map(FileChannel.MapMode.READ_WRITE, 0, 1);

while(true){
  while(mem.get(0)!=5) Thread.sleep(0); // waiting for client request
  mem.put(0, (byte)10); // sending the reply
}

Anmerkungen: Thread.sleep (0) wird benötigt, damit 2 Prozesse die Änderungen des anderen sehen können (ich kenne noch keinen anderen Weg). Wenn zwei Prozesse mit dem Task-Set zum selben Kern gezwungen werden, beträgt die Latenz 1,5 Mikrosekunden - das ist eine Verzögerung beim Kontextwechsel

PPS - und 0,3 Mikrosekunden sind eine gute Zahl! Der folgende Code benötigt genau 0,1 Mikrosekunden, während nur eine primitive Zeichenfolgenverkettung ausgeführt wird:

int j=123456789;
String ret = "my-record-key-" + j  + "-in-db";

PPPS - hoffe, dass dies nicht zu viel vom Thema abweicht, aber schließlich habe ich versucht, Thread.sleep (0) durch Inkrementieren einer statischen flüchtigen int-Variablen zu ersetzen (JVM leert dabei zufällig CPU-Caches) und erhalten - record! - 72-Nanosekunden-Latenz Java-zu-Java-Prozesskommunikation !

Wenn sie jedoch auf denselben CPU-Kern gezwungen werden, können sich flüchtige JVMs niemals gegenseitig steuern, wodurch eine Latenz von genau 10 Millisekunden erzeugt wird. Das Linux-Zeitquantum scheint 5 ms zu betragen. Dies sollte also nur verwendet werden, wenn ein Ersatzkern vorhanden ist. Andernfalls ist Schlaf (0) sicherer.

Andriy
quelle
danke Andriy, sehr Informationsstudie, und sie entspricht mehr oder weniger meinen Messungen für TCP, das ist also eine gute Referenz. Ich denke, ich werde in benannte Rohre schauen.
Bastien
Das Ersetzen des Threads (Sleep) durch Inkrementieren des flüchtigen statischen int sollte also nur erfolgen, wenn Sie einen Prozess an verschiedene Kerne anheften können? Ich wusste auch nicht, dass du das kannst? Ich dachte das Betriebssystem entscheidet?
Mezamorphic
3
Versuchen Sie LockSupport.parkNanos (1), sollte das gleiche tun.
reccles
Sehr schön. Sie können jedoch besser (wie bei 5-7us RTT-Latenz) für TCP-Ping arbeiten. Siehe hier: psy-lob-saw.blogspot.com/2012/12/…
Nitsan Wakart
1
Weitere Untersuchungen zur Verwendung von Speicherzuordnungsdateien als gemeinsam genutzten Speicher zur Unterstützung der IPC-Warteschlange in Java: psy-lob-saw.blogspot.com/2013/04/lock-free-ipc-queue.html Erreichen von 135 Millionen Nachrichten pro Sekunde. Siehe auch meine Antwort unten für eine vergleichende Untersuchung der Latenz nach Methode.
Nitsan Wakart
10

DMA ist eine Methode, mit der Hardwaregeräte auf physischen RAM zugreifen können, ohne die CPU zu unterbrechen. Ein gängiges Beispiel ist beispielsweise ein Festplattencontroller, der Bytes direkt von der Festplatte in den RAM kopieren kann. Als solches gilt es nicht für IPC.

Shared Memory und Pipes werden beide direkt von modernen Betriebssystemen unterstützt. Als solche sind sie ziemlich schnell. Warteschlangen sind normalerweise Abstraktionen, die z. B. über Sockets, Pipes und / oder gemeinsam genutztem Speicher implementiert werden. Dies mag wie ein langsamerer Mechanismus aussehen, aber die Alternative besteht darin, dass Sie eine solche Abstraktion erstellen.

MSalters
quelle
Warum kann ich dann für DMA viele Dinge im Zusammenhang mit RDMA (als Remote Direct Memory Access) lesen, die im gesamten Netzwerk (insbesondere mit InfiniBand) gelten würden, und dasselbe tun? Ich versuche tatsächlich, das Äquivalent OHNE Netzwerk zu erreichen (da sich alle auf derselben Box befinden).
Bastien
RDMA ist das gleiche Konzept: Kopieren von Bytes über ein Netzwerk, ohne die CPUs auf beiden Seiten zu unterbrechen. Es funktioniert immer noch nicht auf Prozessebene.
MSalters
10

Die Frage wurde vor einiger Zeit gestellt, aber Sie könnten an https://github.com/peter-lawrey/Java-Chronicle interessiert sein, das typische Latenzen von 200 ns und Durchsätze von 20 Millionen Nachrichten / Sekunde unterstützt. Es werden speicherabgebildete Dateien verwendet, die von Prozessen gemeinsam genutzt werden (es werden auch die Daten beibehalten, wodurch Daten am schnellsten beibehalten werden können).

Peter Lawrey
quelle
7

Hier ist ein Projekt mit Leistungstests für verschiedene IPC-Transporte:

http://github.com/rigtorp/ipc-bench

sustrik
quelle
Es enthält nicht den 'Java-Faktor', sieht aber interessant aus.
6

Wenn Sie jemals in Betracht ziehen, nativen Zugriff zu verwenden (da sich sowohl Ihre Anwendung als auch der "Server" auf demselben Computer befinden), ziehen Sie JNA in Betracht , da Sie weniger Boilerplate-Code haben, mit dem Sie sich befassen müssen.

Bakkal
quelle
6

Eine späte Ankunft, wollte aber auf ein Open-Source-Projekt hinweisen, das sich der Messung der Ping-Latenz mit Java NIO widmet.

Weiter erforscht / erklärt in diesem Blogbeitrag . Die Ergebnisse sind (RTT in Nanos):

Implementation, Min,   50%,   90%,   99%,   99.9%, 99.99%,Max
IPC busy-spin,  89,    127,   168,   3326,  6501,  11555, 25131
UDP busy-spin,  4597,  5224,  5391,  5958,  8466,  10918, 18396
TCP busy-spin,  6244,  6784,  7475,  8697,  11070, 16791, 27265
TCP select-now, 8858,  9617,  9845,  12173, 13845, 19417, 26171
TCP block,      10696, 13103, 13299, 14428, 15629, 20373, 32149
TCP select,     13425, 15426, 15743, 18035, 20719, 24793, 37877

Dies entspricht der akzeptierten Antwort. Der System.nanotime () -Fehler (geschätzt durch nichts messen) wird bei etwa 40 Nanos gemessen, sodass für den IPC das tatsächliche Ergebnis möglicherweise niedriger ist. Genießen.

Nitsan Wakart
quelle
2

Ich weiß nicht viel über native Kommunikation zwischen Prozessen, aber ich würde vermuten, dass Sie mit nativem Code kommunizieren müssen, auf den Sie über JNI-Mechanismen zugreifen können. Von Java aus würden Sie also eine native Funktion aufrufen, die mit dem anderen Prozess kommuniziert.

Fisch
quelle
1

In meiner früheren Firma haben wir mit diesem Projekt gearbeitet, http://remotetea.sourceforge.net/ , das sehr einfach zu verstehen und zu integrieren ist.

Seffi
quelle
0

Haben Sie darüber nachgedacht, die Steckdosen offen zu halten, damit die Verbindungen wiederverwendet werden können?

Thorbjørn Ravn Andersen
quelle
Die Steckdosen bleiben offen. Die Verbindung ist während der gesamten Laufzeit der Anwendung (ca. 7 Stunden) aktiv. Nachrichten werden mehr oder weniger kontinuierlich ausgetauscht (sagen wir etwa 5 bis 10 pro Sekunde). Die aktuelle Latenz beträgt etwa 200 Mikrosekunden. Ziel ist es, 1 oder 2 Größenordnungen zu sparen.
Bastien
Eine Latenz von 2 ms? Ehrgeizig. Wäre es möglich, das C-Zeug in eine gemeinsam genutzte Bibliothek umzuschreiben, mit der Sie über JNI eine Schnittstelle herstellen können?
Thorbjørn Ravn Andersen
2 ms sind 2000 Mikrosekunden, nicht 200. Dies macht 2 ms weitaus weniger ehrgeizig.
thewhiteambit
-1

Oracle-Fehlerbericht zur JNI-Leistung: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4096069

JNI ist eine langsame Schnittstelle und daher sind Java-TCP-Sockets die schnellste Methode zur Benachrichtigung zwischen Anwendungen. Dies bedeutet jedoch nicht, dass Sie die Nutzdaten über einen Socket senden müssen. Verwenden Sie LDMA, um die Nutzdaten zu übertragen. Wie bereits in früheren Fragen erwähnt , ist die Java-Unterstützung für die Speicherzuordnung nicht ideal, und Sie sollten daher eine JNI-Bibliothek implementieren, um mmap auszuführen.

Steve-o
quelle
3
Warum ist JNI langsam? Überlegen Sie, wie die Low-Level-TCP-Schicht in Java funktioniert. Sie ist nicht in Java-Bytecode geschrieben! (ZB muss dies über den nativen Host geleitet werden.) Daher lehne ich die Behauptung ab, dass Java-TCP-Sockets schneller als JNI sind. (JNI ist jedoch kein IPC.)
4
Ein einzelner JNI-Anruf kostet Sie 9 ns (auf einem Intel i5), wenn Sie nur Grundelemente verwenden. Es ist also nicht so langsam.
Martin Kersten