Warum wir uns für Rust und WebAssembly für Engineering-Berechnungswerkzeuge entschieden haben
Ein technischer Überblick darüber, warum ChainSolve's Berechnungs-Engine in Rust geschrieben und zu WebAssembly kompiliert ist, und was dies für Leistung, Zuverlässigkeit und Portabilität bedeutet.
Die Anforderungen
Bei der Entwicklung von ChainSolve’s Berechnungs-Engine hatten wir eine klare Reihe von Anforderungen:
-
Nahezu native Berechnungsgeschwindigkeit. Engineering-Berechnungen können große Matrizen, iterative Solver und komplexe Abhängigkeitsgraphen mit Hunderten von Knoten beinhalten. Die Engine muss diese in Millisekunden evaluieren, nicht in Sekunden.
-
Browser-Ausführung. Das Werkzeug muss vollständig im Browser funktionieren, ohne Server-Kommunikation für Berechnungen. Ingenieure, die an sensiblen Projekten arbeiten, können proprietäre Berechnungsdaten nicht an externe Server senden.
-
Offline-Fähigkeit. Ingenieure in Testeinrichtungen, auf Fabrikböden und bei Kundenstandorten haben oft begrenzte oder gar keine Internetverbindung. Das Werkzeug muss offline funktionieren, sobald es geladen ist.
-
Speichersicherheit. Ein Berechnungswerkzeug, das abstürzt oder aufgrund von Speicherbeschädigung still falsche Ergebnisse liefert, ist schlimmer als nutzlos. Korrektheit ist nicht verhandelbar.
-
Portabilität. Die gleiche Engine muss in Browsern, in Node.js für CI/CD-Integration und möglicherweise als natives CLI-Werkzeug laufen.
Diese Anforderungen haben das Feld erheblich eingegrenzt.
Warum nicht JavaScript?
JavaScript ist die offensichtliche Wahl für browserbasierte Anwendungen. Es ist die native Sprache des Webs, verfügt über hervorragende Werkzeuge und ein riesiges Ökosystem. Für UI-Code verwenden wir JavaScript (speziell TypeScript mit React). Aber für die Berechnungs-Engine hat JavaScript grundlegende Einschränkungen.
JavaScript ist eine dynamisch typisierte, Garbage-Collection-Sprache. Für UI-Rendering und Event-Handling ist das in Ordnung. Für numerische Berechnungen führt es zu zwei Problemen:
Leistungsdecke. Moderne JavaScript-Engines wie V8 sind bemerkenswert schnell für eine dynamische Sprache, können aber die Leistung von Ahead-of-Time-kompiliertem Code für numerisch intensive Arbeit nicht erreichen. JIT-Kompilation führt zu unvorhersehbaren Pausen. Garbage Collection verursacht Latenz-Spitzen. Dies ist in einer UI-Schicht akzeptabel, aber problematisch in einer Berechnungs-Engine, die konsistente, schnelle Evaluierung braucht.
Numerische Präzision. JavaScript hat einen einzelnen Zahlentyp: IEEE 754 doppelte Genauigkeit Floating-Point. Dies ist für die meisten Engineering-Berechnungen ausreichend, aber JavaScript bietet keine Möglichkeit, das Floating-Point-Verhalten zu steuern, keine SIMD-Intrinsiken für vektorisierte Berechnungen und keine Integer-Typen für Fälle, in denen exakte Integer-Arithmetik erforderlich ist (wie Graphalgorithmen auf dem Abhängigkeitsgraph).
Eine JavaScript-Berechnungs-Engine würde funktionieren. Sie würde für kleine bis mittlere Berechnungen ausreichend sein. Aber sie würde mit komplexen Ketten auf eine Leistungsdecke treffen und würde sorgfältige Umgehungen für numerische Grenzfälle erfordern.
Warum Rust?
Rust erfüllt jede unserer Berechnungsanforderungen:
Leistung. Rust kompiliert zu Maschinencode (oder WebAssembly) ohne Garbage Collector und ohne Laufzeit-Overhead. Numerischer Code in Rust hat vergleichbare Leistung zu C und C++. Für ChainSolve’s Berechnungs-Engine bedeutet dies, dass Chain-Evaluierung konsistent schnell ist, ohne GC-Pausen oder JIT-Aufwärmung.
Speichersicherheit ohne Garbage Collection. Rusts Ownership-System garantiert Speichersicherheit zur Compile-Zeit. Es gibt keine Null-Pointer-Dereferencen, keine Use-After-Free-Bugs, keine Daten-Races. Für ein Berechnungswerkzeug, bei dem Korrektheit paramount ist, ist dies ein erheblicher Vorteil gegenüber C und C++.
WebAssembly-Ziel. Rust hat erstklassige Unterstützung für die Kompilation zu WebAssembly über wasm-pack und wasm-bindgen. Das resultierende WASM-Modul läuft in jedem modernen Browser mit nahezu nativer Geschwindigkeit. Dies gibt uns Browser-Ausführung und Offline-Fähigkeit, ohne Leistung zu opfern.
Starkes Typsystem. Rusts Typsystem erlaubt uns, Engineering-Constraints im Typsystem selbst zu kodieren. Ein UnitValue<Millimeters> kann nicht versehentlich zu einem UnitValue<Inches> addiert werden; der Compiler lehnt es ab. Dies fängt ganze Kategorien von Engineering-Fehlern zur Compile-Zeit ab.
Portabilität. Der gleiche Rust-Code kompiliert zu WASM für Browser-Ausführung, zu nativen Binaries für CLI-Werkzeuge und zu gemeinsamen Bibliotheken für Integration mit anderen Werkzeugen. Ein Codebase, mehrere Ziele.
Die Architektur
ChainSolve’s Architektur trennt Berechnung von Präsentation:
+------------------------------------------+
| Browser (TypeScript + React) |
| - UI-Komponenten |
| - Chain-Editor |
| - Visualisierung |
+------------------------------------------+
| wasm-bindgen-Schnittstelle |
+------------------------------------------+
| WASM-Modul (Rust) |
| - Abhängigkeitsgraph-Solver |
| - Block-Evaluierungs-Engine |
| - Einheiten-Konversionssystem |
| - Expression-Parser |
| - Numerische Methodenbibliothek |
+------------------------------------------+
Das Rust/WASM-Modul zeigt eine saubere API zur TypeScript-Schicht über wasm-bindgen. Die TypeScript-Schicht befasst sich mit allen UI-Belangen, Rendering, Benutzerinteraktion, Layout. Die Rust-Schicht befasst sich mit allen Berechnungen, Graph-Traversierung, Block-Evaluierung, Einheiten-Konversion, Expression-Parsing.
Diese Separation bedeutet, dass die UI nie auf Berechnung blockiert. Chain-Evaluierung geschieht im WASM-Modul, gibt Ergebnisse an TypeScript zurück und die UI wird aktualisiert. Für sehr große Ketten führen wir das WASM-Modul in einem Web Worker aus, um den UI-Thread vollständig responsiv zu halten.
Performance-Ergebnisse
Einige repräsentative Benchmarks aus unseren Development-Builds (gemessen auf einem mittelklasse Laptop, M2 MacBook Air):
| Operation | JavaScript (Baseline) | Rust/WASM |
|---|---|---|
| Evaluate 100-block chain | 12 ms | 0,8 ms |
| Evaluate 1.000-block chain | 340 ms | 8 ms |
| Topological sort (1.000 nodes) | 5 ms | 0,3 ms |
| Unit conversion (10.000 values) | 18 ms | 0,6 ms |
| Expression parse + evaluate | 0,4 ms | 0,02 ms |
Die Rust/WASM-Implementierung ist konsistent 15–40x schneller als das JavaScript-Äquivalent. Für kleine Ketten sind beide schnell genug. Für große Ketten (die Produktionsbenutzer inevitabel erstellen werden), ist der Unterschied zwischen sofortigem Feedback und wahrnehmbarer Verzögerung.
Herausforderungen und Trade-offs
Rust und WebAssembly sind nicht ohne Trade-offs:
Lernkurve. Rust ist eine komplexere Sprache als JavaScript oder Python. Das Ownership-System, obwohl mächtig, erfordert ein mentales Modell, das Zeit zu entwickeln braucht. Für einen Solo-Gründer war dies eine Investition in langfristige Produktivität auf Kosten kurzfristiger Geschwindigkeit.
WASM-Binärgröße. Das kompilierte WASM-Modul ist derzeit etwa 800 KB gzip. Dies ist eine bedeutende Hinzufügung zum initialen Seitenladung. Wir mindern dies mit Lazy Loading, das WASM-Modul wird asynchron nach dem UI-Shell-Rendering geladen.
Debugging. Das Debugging von Rust-Code, der zu WASM kompiliert ist, verbessert sich, aber ist immer noch weniger bequem als das Debugging von JavaScript in Browser DevTools. Wir verlassen uns stark auf Rusts Test-Suite (nativ ausgeführt) und verwenden WASM-spezifisches Logging für Browser-Debugging.
Ökosystem. Das Rust-Ökosystem für numerische Berechnung wächst, aber ist weniger reif als Pythons NumPy/SciPy-Ökosystem. Wir haben einige numerische Methoden von Grund auf implementiert, wo bestehende Rust-Crates unzureichend waren.
Trotz dieser Trade-offs ist die Entscheidung, Rust und WebAssembly zu verwenden, durch die Performance- und Zuverlässigkeitsmerkmale der resultierenden Engine validiert worden. Für ein Werkzeug, bei dem Ingenieure sich auf die Ergebnisse für sicherheitskritische Entscheidungen verlassen, sind die Compile-Zeit-Garantien, die Rust bietet, die zusätzliche Entwicklungskomplexität wert.
Fazit
Die Wahl von Rust und WebAssembly für ChainSolve’s Berechnungs-Engine war eine absichtliche architektonische Entscheidung, getrieben von Engineering-Anforderungen, nicht von Technologie-Mode. Nahezu native Leistung im Browser, Speichersicherheit ohne Garbage Collection und ein starkes Typsystem, das Einheitenfehler zur Compile-Zeit fängt — diese sind nicht Nice-to-Haves für ein Engineering-Berechnungswerkzeug. Sie sind Anforderungen.
Wenn Sie an den technischen Details unserer Rust/WASM-Architektur interessiert sind oder ähnliche Technologie-Entscheidungen für Ihre eigenen Engineering-Werkzeuge evaluieren, heißen wir das Gespräch willkommen. Kontaktieren Sie uns über unsere Kontaktseite.