Vergleich der Netzwerktreiberleistung in 10 Programmiersprachen

Eine Gruppe von Forschern deutscher Universitäten опубликовала Befund das ExperimentDabei wurden 10 Varianten eines typischen Treibers für 10-Gigabit Intel Ixgbe (X5xx) Netzwerkkarten in verschiedenen Programmiersprachen entwickelt. Der Treiber läuft im Userspace und ist in C, Rust, Go, C#, Java, OCaml, Haskell, Swift, JavaScript und Python implementiert. Beim Schreiben des Codes lag der Fokus auf der Erzielung einer möglichst hohen Performance unter Berücksichtigung der Besonderheiten jeder Sprache. Vom Funktionsumfang her sind alle Optionen identisch und bestehen aus ca. 1000 Codezeilen. Projekterfolge Ausbreitung unter der BSD-Lizenz.

Die Leistung der Rust-Version des Treibers kam der des Referenz-C-Treibers sehr nahe. Bei einer Belastung mit einem einzigen Versand von Blöcken zu je 32 Paketen blieb der Rust-Treiber etwas zurück, aber in Tests mit mehr als 32 Paketen pro Block unterschied er sich in der Geschwindigkeit praktisch nicht vom C-Treiber und zeigte Leistung auf Verarbeitungsebene 28 Millionen Pakete pro Sekunde auf einem Server mit einer Xeon-CPU E3-1230 v2 3.3 GHz.

Vergleich der Netzwerktreiberleistung in 10 Programmiersprachen

Die nächste Nische in Bezug auf die Leistung wurde von Go- und C#-Treibern besetzt, die ziemlich ähnliche Ergebnisse zeigten (der Go-Treiber gewann in Tests mit Blöcken, die bis zu 16 Pakete enthielten, und begann in Tests mit mehr als 16 Paketen in einem leicht zu verlieren). Block). Bei 256 Paketen pro Block betrug die Spitzenleistung für den C#-Treiber etwa 28 Mpps und für die Go-Treiber etwa 25 Mpps.

Als nächstes mit ziemlich knappen Ergebnissen, gefolgt von Treibern für
Java, OCaml und Haskell, die bereits deutlich hinter den zuvor betrachteten Optionen zurückblieben und die Marke von 12 Millionen Paketen pro Sekunde nicht überwinden konnten. Einen noch größeren Rückstand zeigten Treiber auf Basis von Swift und JavaScript, die Streams mit einer Geschwindigkeit von 5 Millionen Paketen pro Sekunde verarbeiten konnten.

Die Wertung schloss der Treiber in der Sprache Python ab, der lediglich 0.14 Millionen Pakete pro Sekunde verarbeiten konnte. Die Python-Implementierung wurde verwendet, um die Geschwindigkeit von Interpretern ohne JIT und ohne spezifische Optimierungen zu bewerten (der Code wurde mit CPython 3.7 ausgeführt und war nicht mit PyPy kompatibel, es wird jedoch darauf hingewiesen, dass die Optimierung von Datenstrukturen die Leistung um etwa das Zehnfache verbessern könnte).

Zusätzlich wurden Latenztests durchgeführt, die die Wirksamkeit der Pufferung und die Auswirkung des Garbage Collectors zeigten. Der Test maß die Latenz nach der Umleitung jedes Pakets durch den Treiber im Vergleich zu einer bekannten Sendezeit. Spitzenreiter waren weiterhin die C- und Rust-Treiber, deren Ergebnisse bei einem Fluss von 1 Million Paketen pro Sekunde (ca. 20 µs) nahezu nicht zu unterscheiden waren. Eine gute Leistung erbrachte der Treiber in der Go-Sprache, der nur knapp hinter den Spitzenreitern zurückblieb und sich ebenfalls auf dem Niveau von 20 µs hielt. Der C#-Treiber zeigte Verzögerungen von etwa 50 µs.
Treiber auf Basis von JavaScript und Java zeigten die größten Verzögerungen (Verzögerungen über 300 µs).

Vergleich der Netzwerktreiberleistung in 10 Programmiersprachen

Die Studie wurde durchgeführt, um die Möglichkeit der Entwicklung von Treibern und Betriebssystemkomponenten in Sprachen mit einem höheren Niveau als C zu bewerten. Derzeit sind 39 von 40 Linux-Speicherproblemen treiberbedingt, also die Probleme bei der Einführung einer sichereren Sprache und der Verlagerung von Treibern aus dem Kernel in den Benutzerbereich bleiben relevant und Hersteller experimentieren bereits aktiv in diese Richtung (zum Beispiel hat Google einen TCP-Stack für OS entwickelt Fuchsia in Go, CloudFlare erstellt Durch die Implementierung des QUIC-Protokolls in Rust hat Apple den TCP-Stack auf Mobilgeräten in den Userspace verschoben.

Im Zuge der durchgeführten Arbeiten kam man zu dem Schluss, dass die Rust-Sprache der beste Kandidat für die Treiberentwicklung ist. Die von Rust bereitgestellten Funktionen ermöglichen es Ihnen, die Probleme zu beseitigen, die durch die Verarbeitung von Speicher auf niedriger Ebene entstehen, allerdings auf Kosten einer Leistungseinbuße von etwa 2–10 % im Vergleich zu C-Sprachtreibern. Go und C# haben sich auch als geeignet für die Erstellung von Systemkomponenten in Situationen erwiesen, in denen eine durch den Garbage Collector verursachte Latenz von unter einer Millisekunde akzeptabel ist.

Source: opennet.ru

Kommentar hinzufügen