30 Punkte von xguru 2023-05-24 | 9 Kommentare | Auf WhatsApp teilen
  • Vergleich des Speicherverbrauchs von Asynchronität und Multithreading in Rust, Go, Java, C#, Python, Node.js und Elixir
  • Für jede Sprache wurde ein Programm geschrieben (mit Hilfe von ChatGPT), das N Tasks ausführt, die 10 Sekunden warten
  • Vergleich auf Xeon E3 + Ubuntu 22.04

Ergebnisse

  • Minimaler Footprint (Test mit nur 1 Task): Go und Rust benötigen weniger als 3 MB, Python 17 MB, Java/Node.js etwa 40 MB, C# 131 MB
  • 10.000 Tasks: Rust Tokio 4,6 MB, Rust async-std 8 MB, Go 28,6 MB, Python 40 MB, Rust Threads 48 MB, Node.js 48 MB, Java Virtual Thread 78 MB, Elixir 99 MB, C# 131 MB, Java Threads 244 MB
  • 100.000 Tasks (ohne Threads): Rust tokio 23 MB, Rust Async-std 54 MB, Node.js 112 MB, C# 130 MB, Java Virtual Threads 223 MB, Python 240 MB, Go 269 MB, Elixir 445 MB
  • 1 Million Tasks: Rust Tokio 213 MB, C# 461 MB, Node.js 494 MB, Rust async-std 527 MB, Java Virtual Threads 1154 MB, Python 2232 MB, Go 2658 MB, Elixir 4009 MB

Fazit

  • Rust Tokio ist konkurrenzlos
  • C# hat zwar einen großen Footprint, ist aber sehr konkurrenzfähig (und schlägt teilweise sogar Rust)
  • Bei 1 Million Tasks vergrößert sich der Abstand von Go zu Javas virtuellen Threads (und stellt die verbreitete Annahme auf den Kopf, dass Go leichter als die JVM sei)
  • Es wurde nur der Speicherverbrauch betrachtet; andere Faktoren wurden nicht berücksichtigt
  • Bei 1 Million Tasks steigt der Overhead beim Starten der Arbeit stark an, und die meisten Programme benötigen mehr als 12 Sekunden bis zum Abschluss
  • Weitere Benchmarks sind geplant

9 Kommentare

 
bus710 2023-05-25

Das ist ein ziemlich aussagekräftiger Benchmark, wenn man Go verwendet, immer wieder zu Rust hinüberschielt und sich fragt, ob man sich wirklich an diese strenge Syntax gewöhnen muss. Wenn Rust selbst in Situationen, in denen Go wegen OOM abstürzt, stabil durchhält, wäre das die Investition definitiv wert.
Natürlich bleibt das Problem bestehen, dass es deutlich schwieriger ist, Rust-Entwickler zu finden...

 
kuber 2023-05-24

Es stimmt, dass Go im Nachteil ist, je mehr Threads es gibt, weil für jede einzelne Goroutine ein eigener Stack (2 KB) zugewiesen wird und der Speicherverbrauch dadurch um O(n) steigt....

Was mich nebenbei neugierig macht: Wie oft gibt es überhaupt Situationen mit mehr als 10.000 Threads? Es wirkt, als würden Context Switches häufiger auftreten als dass der eigentliche Code läuft....

 
kotliner 2023-05-24

Ich bin neugierig, wie es sich mit Kotlin-Coroutines verhält.

 
[Dieser Kommentar wurde ausgeblendet.]
 
secret3056 2023-05-24

Das Ergebnis von Elixir ist am überraschendsten; ich dachte, dass Erlang im Vergleich zu Go nur Speicher im Bereich von einigen hundert Wörtern benötigt, also sogar leichter ist ...

 
kunggom 2023-05-24

Ich habe in der offiziellen Erlang-Dokumentation nachgesehen; dort steht, dass zum Spawnen eines einzelnen Erlang-Prozesses 338 Wörter benötigt werden. Da auf einem 64-Bit-System 1 Wort 8 Byte entspricht, würde ein Erlang-Prozess also etwa 2,7 KB (338 × 8 = 2.704) Speicher belegen. Da die Größe eines einzelnen goroutine-Stacks in Go bei etwa 2,0 KB liegt, scheint Erlang in dieser Hinsicht mehr Speicher zu verbrauchen.

Dann müssten bei einer einfachen Rechnung 1 Million Erlang-Prozesse 2,7 GB Speicher belegen. Im oben vorgestellten Elixir-Benchmark wurde jedoch eine maximale Speichernutzung von etwa 4,0 GB beobachtet, also wurden 1,3 GB mehr Speicher verwendet. Rein rechnerisch bedeutet das in diesem Szenario, dass pro Erlang-Prozess zusätzlich 1,3 KB Speicher genutzt wurden. Ich weiß es nicht genau, aber vielleicht benötigt die Runtime irgendeinen zusätzlichen Speicherbereich, sobald die Anzahl der Erlang-Prozesse einen bestimmten Grenzwert überschreitet.

 
bus710 2023-05-25

Ich vermute, es liegt daran, dass Kapazitäten für Supervision-Tree-Maps oder Message-Queues vorab reserviert werden.

 
humblebee 2023-05-24

Ich finde, Rust ist wirklich eine großartige Sprache – vom Paradigma bis zur Performance.

 
xguru 2023-05-24

Vergleiche zwischen asynchronen und Thread-basierten Ansätzen sowie Benchmarks, in die auch Sprach-Runtimes einfließen, können je nach Perspektive unterschiedlich ausfallen – bitte als Referenz betrachten.
Schauen Sie sich dazu auch die Kommentare auf HN an. https://news.ycombinator.com/item?id=36024209