2012-08-23 15 views
9

Ich mache einige Benchmarks mit einem optimierten Java NIO Selektor auf Linux über Loopback (127.0.0.1).Java NIO Selektor minimal mögliche Latenz

Mein Test ist sehr einfach:

  • Ein Programm sendet ein UDP-Paket an ein anderes Programm, das es zurück an den Absender und die Umlaufzeit berechnet wird Echo. Das nächste Paket wird nur gesendet, wenn das vorherige Paket bestätigt wurde (wenn es zurückkehrt). Ein richtiges Aufwärmen mit ein paar Millionen Nachrichten wird durchgeführt, bevor der Benchmark durchgeführt wird. Die Nachricht hat 13 Byte (ohne UDP-Header).

Für die Umlaufzeit ich die folgenden Ergebnisse erhalten:

  • Min Zeit: 13 Mikros
  • Avg Zeit: 19 Mikros
  • 75% Perzentil: 18.567 Nanos
  • 90% Perzentil: 18.789 Nanometer
  • 99% Perzentil: 19.184 Nanometer
  • 99,9% Perzentil: 19,264 Nanos
  • 99,99% Perzentil: 19.310 Nanos
  • 99,999% Perzentil: 19.322 Nanos

Aber der Haken dabei ist, dass ich Spinnen 1 Million Nachrichten.

Wenn ich nur 10 Nachrichten spinnen erhalte ich sehr unterschiedliche Ergebnisse:

  • Min Zeit: 41 Mikros
  • Avg Zeit: 160 Mikros
  • 75% Perzentil: 150.701 nanos
  • 90% Perzentil : 155.274 nanos
  • 99% Perzentil: 159.995 nanos
  • 99,9% Perzentil: 159.995 nanos
  • 99,99% Perzentil: 159.995 nanos
  • 99,999% Perzentil: 159.995 nanos

mich korrigieren, wenn ich falsch bin, aber ich vermute, dass, wenn wir den NIO-Selektor Spinnen die Reaktionszeiten werden optimal erhalten. Wenn wir jedoch Nachrichten mit einem ausreichend großen Intervall zwischen ihnen senden, zahlen wir den Preis für das Aufwecken des Selektors.

Wenn ich mit dem Senden nur einer einzigen Nachricht herumspiele, bekomme ich verschiedene Zeiten zwischen 150 und 250 Mikros.

Also meine Fragen für die Gemeinschaft sind:

1 - Ist meine Mindestzeit von 13 Mikros mit durchschnittlich 19 Mikros optimal für diese Rundfahrt Paket Test. Es sieht so aus, als würde ich ZeroMQ bei weitem schlagen, damit ich hier etwas vermisse.Von diesem Benchmark es wie ZeroMQ aussieht hat eine 49 Mikros avg Zeit (99% Perzentil) auf einem Standard-Kernel =>http://www.zeromq.org/results:rt-tests-v031

2 - Gibt es etwas, was ich tun kann die Wahlreaktionszeit zu verbessern, wenn ich ein einzelnen oder sehr spinnen einige Nachrichten? 150 Mikros sieht nicht gut aus. Oder sollte ich davon ausgehen, dass auf einer Prod-Umgebung der Selektor nicht ganz sein wird?


Durch beschäftigt drehen um selectNow() kann ich bessere Ergebnisse erzielen. Das Senden von wenigen Paketen ist immer noch schlimmer als das Senden vieler Pakete, aber ich denke, dass ich jetzt die Selektor-Leistungsgrenze erreiche. Meine Ergebnisse:

  • Senden eines einzelnen Pakets bekomme ich eine konsistente 65 Mikro-Umlaufzeit.
  • Mit zwei Paketen erreiche ich im Durchschnitt 39 Mikro-Round-Trip-Zeit.
  • Mit 10 Paketen erreiche ich durchschnittlich 17 Mikro-Round-Trip-Zeit.
  • Durch das Senden von 10.000 Paketen erreiche ich im Durchschnitt eine Umlaufzeit von 10.098 Nanosekunden.
  • Senden 1 Million Pakete bekomme ich im Durchschnitt 9.977 Nanos Umlaufzeit.

Schlussfolgerungen

  • So sieht es aus wie die physische Barriere für die UDP-Paket Rundfahrt ist ein Durchschnitt von 10 Mikrosekunden, obwohl ich einige Pakete bekam die Reise in 8 Mikros (min Zeit) machen .

  • Mit viel Spinnerei (danke Peter) konnte ich im Durchschnitt von 200 Mikros auf durchschnittlich 65 Mikros für ein einzelnes Paket gehen.

  • Nicht sicher, warum ZeroMQ ist 5 times slower als das. (Edit: Vielleicht, weil ich mich entschieden diese auf derselben Maschine durch Loopback und ZeroMQ wird zwei verschiedene Maschinen verwenden?)

+0

Ich denke, dass viel davon auf HotSpot JVM Aufwärmzeiten eher als das Verhalten von Selektoren spezifisch zurückzuführen ist. – EJP

+1

Danke @EJP, aber ich habe mit dem JVM im Server-Modus etwas Warmup gemacht. Ich habe ein paar Millionen Nachrichten gesendet, bevor ich die Nachrichten gesendet habe, die den Benchmark auslösen werden. Warum denkst du das passiert? => "Wenn ich mit dem Senden einer einzigen Nachricht herumspiele, bekomme ich verschiedene Zeiten zwischen 150 und 250 Mikros." – Julie

+0

rufen Sie mich verrückt, aber warum nicht einfach implementieren Sie Ihr (aus Beschreibung) Kurzprogramm in C und sehen Sie die Leistung. – NoSenseEtAl

Antwort

4

Sie sehen oft Fälle ein Faden Erwachen kann sehr teuer sein, nicht nur, weil es braucht Zeit für den Thread aufwachen, aber der Thread läuft 2-5x langsamer für einige zehn Mikrosekunden später als die Caches und

Die Art, wie ich dies in der Vergangenheit vermieden habe, ist zu beschäftigt warten. Leider erstellt SelectNow bei jedem Aufruf eine neue Sammlung, auch wenn es sich um eine leere Sammlung handelt. Dies erzeugt so viel Müll, dass es nicht wert ist, verwendet zu werden.

Ein Weg um dies zu tun beschäftigt auf nicht blockierende Sockets warten. Dies skaliert nicht besonders gut, kann aber die niedrigste Latenzzeit ergeben, da der Thread nicht aufwachen muss und der Code, den Sie danach ausführen, sich eher im Cache befindet. Wenn Sie auch Thread-Affinität verwenden, kann dies die Thread-Störung reduzieren.

Was ich auch vorschlagen würde ist zu versuchen, Ihre Code-Sperre weniger und Müll weniger. Wenn Sie dies tun, können Sie einen Prozess in Java haben, der 90% der Zeit eine Antwort auf ein eingehendes Paket unter 100 Mikrosekunden sendet. Dies würde es ermöglichen, jedes Paket bei 100 Mb zu verarbeiten, wenn sie ankommen (bis zu 145 Mikrosekunden auseinander aufgrund von Bandbreiteneinschränkungen). Für eine 1 Gb Verbindung können Sie ziemlich nah kommen.


Wenn Sie schnelle Kommunikation zwischen Prozessen auf dem gleichen Feld in Java wollen, könnten Sie überlegen, so etwas wie https://github.com/peter-lawrey/Java-Chronicle Dies verwendet Shared Memory passieren Nachrichten mit Round-Trip-Latenzen (die effizient mit Steckdosen zu tun härter ist) von weniger als 200 Nanosekunden. Es behält auch die Daten bei und ist nützlich, wenn Sie nur eine schnelle Möglichkeit haben möchten, eine Journaldatei zu erstellen.

+0

Hallo Peter. Bitte sehen Sie meine neuen Ergebnisse basierend auf Ihren Kommentaren. Irgendeine Idee, warum ZeroMQ ist 5 mal langsamer als das? – Julie

+0

ZeroMQ muss mehr tun, als nur ein Paket auf einem einzelnen Socket zu senden. Es muss mehr arbeiten, routing usw., so dass die Latenz höher ist. Ich vermute auch, dass es einen Hintergrund-Thread verwendet, um den Empfang zu tun, was die Verwaltbarkeit und die Kontrolle über Verbindungen verbessert (oder zumindest viele dieser Bibliotheken). Eine der Kompromisse, die man oft sieht, ist, dass man Nachrichten mit einem sendenden Thread stapelweise verarbeiten kann Erhöhen Sie den Durchsatz um den Faktor 10, auf den sich viele Bibliotheken konzentrieren, anstatt auf Latenzzeiten. –

+0

Ich vermute, der Unterschied liegt daran, dass ich dies über LOOPBACK teste. Ich versuche, ZeroMQ-Benchmarks über Loopback zu vergleichen. Ein Sende-Thread !? Das ist furchtbar! Warum kannst du nicht einfach Channel Write aufrufen und das OS den Rest erledigen lassen? Für niedrige Latenz ist etwas anderes als NIO nicht sinnvoll IMHO. – Julie

-1

Wenn Sie den Selektor richtig einstellen, können Sie die Kommunikation zwischen Sockets in Java in weniger als 2 Mikrosekunden durchführen. Hier sind meine einzige Weg, um Ergebnisse für ein 256-Byte-UDP-Paket:

Iterations: 1,000,000 
Message Size: 256 bytes 
Avg Time: 1,680 nanos 
Min Time: 1379 nanos 
Max Time: 7020 nanos 
75%: avg=1618 max=1782 nanos 
90%: avg=1653 max=1869 nanos 
99%: avg=1675 max=1964 nanos 
99.9%: avg=1678 max=2166 nanos 
99.99%: avg=1679 max=5094 nanos 
99.999%: avg=1680 max=5638 nanos 

Ich rede mehr über Java NIO und dem Reactor in meinem Artikel Inter-socket communication with less than 2 microseconds latency.

+4

Es ist eine Schande, dass der Artikel nicht wirklich sagt, wie du es getan hast ... –