Java NIO Selektor minimal mögliche Latenz

9

Ich mache einige Benchmarks mit einem optimierten Java NIO-Selektor unter Linux-Loopback (127.0.0.1).

Mein Test ist sehr einfach:

  • Ein Programm sendet ein UDP-Paket an ein anderes Programm, das es zurück an den Sender sendet, und die Umlaufzeit wird berechnet. 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 Rundreisezeit bekomme ich folgende Ergebnisse:

  • Minimale Zeit: 13 Mikros
  • Durchschn. Zeit: 19 Mikros
  • 75% Perzentil: 18.567 Nanometer
  • 90% Perzentil: 18.789 Nanometer
  • 99% Perzentil: 19.184 Nanometer
  • 99,9% Perzentil: 19,264 Nanometer
  • 99,99% Perzentil: 19.310 Nanometer
  • 99,999% Perzentil: 19.322 Nanometer

Aber der Haken ist, dass ich 1 Million Nachrichten spinne.

Wenn ich nur 10 Nachrichten spinne, bekomme ich sehr unterschiedliche Ergebnisse:

  • Min Zeit: 41 Mikros
  • Durchschn. Zeit: 160 Mikros
  • 75% Perzentil: 150.701 Nanometer
  • 90% Perzentil: 155,274 Nanometer
  • 99% Perzentil: 159.995 Nanometer
  • 99,9% Perzentil: 159,995 Nanometer
  • 99,99% Perzentil: 159,995 Nanometer
  • 99,999% Perzentil: 159,995 Nanometer

Korrigiere mich, wenn ich falsch liege, aber ich vermute, dass sobald wir den NIO-Selektor drehen, die Antwortzeiten optimal werden. 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 einer einzelnen 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 für diesen Round-Trip-Paket-Test optimal? Es sieht so aus, als ob ich ZeroMQ bei weitem prügele, damit ich hier etwas verpasse. Aus diesem Benchmark sieht es so aus, als ob ZeroMQ eine 49 Mikro-Zeit (99% Perzentil) auf einem Standard-Kernel hat = & gt; Ссылка

2 - Kann ich etwas tun, um die Reaktionszeit des Selektors zu verbessern, wenn ich eine einzelne oder sehr wenige Nachrichten spinne? 150 Mikros sieht nicht gut aus. Oder sollte ich davon ausgehen, dass der Selektor in einer Prod-Umgebung nicht ganz stimmt?

Indem ich beschäftigt bin, mich um selectNow () zu drehen, 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:

  • Wenn ich ein einzelnes Paket sende, bekomme ich eine konsistente Rundroute von 65 Mikros.
  • Beim Senden von zwei Paketen erreiche ich im Durchschnitt 39 Mikro-Round-Trip-Zeit.
  • Beim Senden von 10 Paketen erreiche ich im Durchschnitt eine durchschnittliche Umlaufzeit von 17 Mikros.
  • Wenn ich 10.000 Pakete sende, bekomme ich durchschnittlich 10.098 Nanos Umlaufzeit.
  • Wenn ich 1 Million Pakete sende, erhalte ich durchschnittlich 9.977 Nanosekunden.

Schlussfolgerungen

  • Es sieht also so aus, als ob die physikalische Barriere für die UDP-Paketumleitung im Durchschnitt 10 Mikrosekunden beträgt, obwohl ich einige Pakete erhalten habe, die den Trip 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 fünfmal so langsam ist . ( Bearbeiten: Vielleicht, weil ich dies auf dem gleichen Rechner über Loopback teste und ZeroMQ zwei verschiedene Rechner benutzt?)

Julie 23.08.2012, 20:26
quelle

2 Antworten

4

Sie sehen oft, dass Fälle, in denen ein Thread aufwacht, sehr teuer sein können, nicht nur weil der Thread Zeit braucht, um aufwachen, aber der Thread läuft 2-5x langsamer für einige zehn Mikrosekunden als die Caches und

Die Art, wie ich das in der Vergangenheit vermieden habe, ist beschäftigt zu 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.

Um dies zu umgehen, muss man auf nicht blockierende Sockets warten. Dies lässt sich nicht besonders gut skalieren, bietet Ihnen jedoch die geringste Latenzzeit, da der Thread nicht aufwachen muss und der Code, den Sie danach ausführen, eher im Cache vorhanden ist. Wenn Sie auch Thread-Affinität verwenden, kann es Ihre Threads-Störung reduzieren.

Ich würde auch vorschlagen, dass Sie versuchen, Ihre Code-Sperre weniger und weniger überflüssig zu machen. 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 Ihnen ermöglichen, jedes Paket bei 100 Mb zu verarbeiten, wenn sie ankommen (bis zu 145 Mikrosekunden Abstand aufgrund von Bandbreiteneinschränkungen). Für eine 1 Gb-Verbindung können Sie ziemlich nahe kommen.

Wenn Sie eine schnelle Interprozesskommunikation auf derselben Box in Java wünschen, könnten Sie etwas wie Ссылка in Betracht ziehen Gemeinsamer Speicher zum Übergeben von Nachrichten mit Round-Trip-Latenzzeiten (was mit Sockets schwieriger zu tun 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.

    
Peter Lawrey 24.08.2012 09:39
quelle
-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 One-Way-Ergebnisse für ein 256-Byte-UDP-Paket:

%Vor%

Ich spreche mehr über Java NIO und das Reaktormuster in meinem Artikel Inter-Socket-Kommunikation mit weniger als 2 Mikrosekunden Latenz .

    
TraderJoeChicago 16.02.2013 01:36
quelle

Tags und Links