11 Punkte von GN⁺ 2026-03-21 | 2 Kommentare | Auf WhatsApp teilen
  • Der in Rust geschriebene WASM-Parser ist strukturell schnell, doch der Overhead durch Datenkopien und Serialisierung an der JS-WASM-Grenze erwies sich als Performance-Engpass
  • Die direkte Objektrückgabe über serde-wasm-bindgen war 9 bis 29 % langsamer als die JSON-Serialisierung, was auf die feingranularen Konvertierungskosten zwischen den Laufzeitumgebungen zurückzuführen ist
  • Nach dem Portieren der gesamten Pipeline nach TypeScript wurde bei gleicher Architektur eine 2,2- bis 4,6-mal schnellere Performance pro Einzelaufruf erreicht
  • Bei der Streaming-Verarbeitung wurde durch die Verbesserung von O(N²) auf O(N) mittels satzweisem Caching eine 2,6- bis 3,3-mal schnellere Gesamtverarbeitung erzielt
  • Im Ergebnis zeigte sich, dass WASM für rechenintensive, selten aufgerufene Aufgaben geeignet ist, für JS-Objekt-Parsing oder häufig aufgerufene Funktionen hingegen weniger

Struktur und Grenzen des Rust-WASM-Parsers

  • Der Parser openui-lang besteht aus einer 6-stufigen Pipeline, die von einem LLM erzeugte DSL in einen React-Komponentenbaum umwandelt
    • Schritte: autocloser → lexer → splitter → parser → resolver → mapper → ParseResult
    • Jede Stufe übernimmt Aufgaben wie Tokenisierung, Syntaxanalyse, Variablenauflösung und AST-Transformation
  • Der Rust-Code selbst ist schnell, doch bei jedem Aufruf fallen Zeichenkettenkopien sowie JSON-Serialisierung und -Deserialisierung zwischen JS und WASM an
    • Kopie des Eingabestrings (JS→WASM), internes Parsing in Rust, JSON-Serialisierung des Ergebnisses, JSON-Kopie (WASM→JS), Deserialisierung in JS
  • Dieser Grenz-Overhead dominierte die Gesamtperformance; die Rechengeschwindigkeit von Rust war nicht der Flaschenhals

Versuch mit serde-wasm-bindgen und warum er scheiterte

  • Um die JSON-Serialisierung zu vermeiden, wurde serde-wasm-bindgen eingesetzt, das Rust-Strukturen direkt als JS-Objekte zurückgibt
  • Beobachtet wurde jedoch eine 30 % schlechtere Performance
    • JS kann den Speicher von Rust-Strukturen nicht direkt lesen, und da sich die Speicherlayouts der Laufzeitumgebungen unterscheiden, ist eine feldweise Konvertierung erforderlich
    • Bei der JSON-Serialisierung hingegen wird in Rust einmal ein String erzeugt, der in JS mit dem optimierten JSON.parse verarbeitet wird
  • Benchmark-Ergebnisse
    Fixture JSON round-trip serde-wasm-bindgen Veränderung
    simple-table 20.5µs 22.5µs -9%
    contact-form 61.4µs 79.4µs -29%
    dashboard 57.9µs 74.0µs -28%

Wechsel zu TypeScript und Performance-Gewinn

  • Die gleiche 6-stufige Struktur wurde vollständig nach TypeScript portiert, wodurch die WASM-Grenze entfiel und alles direkt im V8-Heap ausgeführt werden konnte
  • Benchmark-Ergebnisse pro Einzelaufruf
    Fixture TypeScript WASM Geschwindigkeitsgewinn
    simple-table 9.3µs 20.5µs 2.2x
    contact-form 13.4µs 61.4µs 4.6x
    dashboard 19.4µs 57.9µs 3.0x
  • Allein das Entfernen von WASM reduzierte die Kosten pro Aufruf deutlich, allerdings blieb die Ineffizienz der Streaming-Struktur bestehen

Das O(N²)-Problem beim Streaming-Parsing und seine Verbesserung

  • Wenn LLM-Ausgaben in mehreren Chunks geliefert werden, entsteht die O(N²)-Ineffizienz, weil bei jedem Schritt der gesamte kumulierte String erneut geparst wird
    • Beispiel: Ein Dokument mit 1000 Zeichen wird 50-mal in 20-Zeichen-Stücken geparst → insgesamt 25.000 verarbeitete Zeichen
  • Als Lösung wurde inkrementelles Caching auf Satzebene eingeführt
    • Abgeschlossene Sätze werden gecacht, nur der noch unvollständige Satz wird erneut geparst
    • Der gecachte AST wird mit dem neuen AST zusammengeführt und als Ergebnis zurückgegeben
  • Benchmarks für den gesamten Stream
    Fixture Naives TS Inkrementelles TS Geschwindigkeitsgewinn
    simple-table 69µs 77µs keiner
    contact-form 316µs 122µs 2.6x
    dashboard 840µs 255µs 3.3x
  • Je mehr Sätze vorhanden sind, desto größer ist der Cache-Effekt, und der Gesamtdurchsatz verbessert sich linear

Erkenntnisse zum Einsatz von WASM

  • Geeignete Fälle
    • Rechenintensive Aufgaben mit wenig Interaktion: Bild- und Videoverarbeitung, Kryptografie, Physiksimulationen, Audio-Codecs usw.
    • Portierung bestehender nativer Bibliotheken: SQLite, OpenCV, libpng usw.
  • Ungeeignete Fälle
    • Parsing strukturierter Texte in JS-Objekte: Hier dominieren die Serialisierungskosten
    • Funktionen mit kurzen Eingaben und sehr häufigen Aufrufen: Die Grenzkosten sind größer als die eigentliche Berechnung
  • Zentrale Lehren
    1. Die Sprache sollte erst nach Profiling des tatsächlichen Flaschenhalses gewählt werden
    2. Die direkte Objektübergabe mit serde-wasm-bindgen ist teurer
    3. Die Verbesserung der algorithmischen Komplexität ist wirkungsvoller als ein Sprachwechsel
    4. WASM und JS teilen sich keinen Heap, daher gibt es immer Konvertierungskosten

Endergebnis: Durch den Wechsel zu TypeScript und inkrementelles Caching wurde eine 2,2- bis 4,6-mal bessere Performance pro Aufruf sowie eine 2,6- bis 3,3-mal bessere Performance über den gesamten Stream erreicht

2 Kommentare

 
bbulbum 2026-03-23

War das nicht vielleicht eher als ironische Spitze gegen einen hochgradig auf Performance-Optimierung in Rust fokussierten Artikel gemeint..

 
GN⁺ 2026-03-21
Hacker-News-Kommentare
  • Der eigentliche Kern ist nicht TypeScript statt Rust, sondern die Korrektur des Streaming-Algorithmus von O(N²) auf O(N)
    Allein diese Änderung mit Caching auf Statement-Ebene brachte bereits eine 3,3-fache Verbesserung
    Unabhängig von der Sprachwahl ist das aus Sicht der Nutzer der Hauptgrund für die spürbare Verbesserung der Latenz
    Der Titel wirkt, als würde er diesen interessanten Engineering-Punkt unterbewerten

    • Beim uv-Projekt ist es genauso. Die Leute rufen nur „rust rulez!“, aber der tatsächliche Gewinn kommt nicht von der Sprache, sondern von algorithmischen Verbesserungen
    • Danke, dass du dich durch den Clickbait gearbeitet und den Kern herausgestellt hast
      Der Beitrag selbst ist interessant, aber ich bin in letzter Zeit von übertriebenen Clickbait-Titeln genervt
    • Die Darstellung als n² scheint etwas übertrieben
      Es werden die Zeiten einzelner Aufrufe gemessen und der Median verwendet, aber in Browser-Umgebungen mit Timing-Angriffsschutz in der JS-Engine habe ich Zweifel an der Genauigkeit
    • Letztlich halte ich es für einen irreführenden Titel
  • „Ich habe Code von Sprache L nach M umgeschrieben und er wurde schneller“ ist kaum überraschend
    Denn dabei bekommt man die Gelegenheit, Verhakungen und Fehlentscheidungen zu beseitigen und einen neu entstandenen besseren Ansatz anzuwenden
    Tatsächlich gilt das sogar bei L=M: Der Geschwindigkeitsgewinn kommt nicht von der Sprache, sondern aus dem Prozess von Rewrite und Redesign

    • Wenn nun ein Dritter die TypeScript-Version in Rust neu schreibt, ohne das Original zu kennen, steigt die Performance vielleicht noch einmal
    • Ich sehe oft, dass selbst beim Neuaufschreiben in derselben Sprache ein Verbesserungseffekt entsteht
  • Ich habe tiefer hineingeschaut, um die Performance der Objektserialisierung an der Grenze zwischen Rust und JS zu verbessern
    Der Ansatz von serde wirkte aus Performance-Sicht ungünstig, und ich habe einen Versuch zu dessen Verbesserung in meinem Blogbeitrag beschrieben

  • Ich hatte mich gefragt, warum Open UI nichts im WASM-Bereich macht
    Dann war ich verwirrt, weil diese neue Firma den Namen Open UI verwendet
    Die eigentliche Open UI W3C Community Group ist seit über fünf Jahren die Gruppe, die Standards für Dinge wie HTML-Popover, anpassbare Selects, Invoker-Commands und Accordions entwickelt
    Sie leisten wirklich hervorragende Arbeit

  • Es heißt, man habe serde-wasm-bindgen integriert, um den „JSON-Roundtrip zu überspringen“, aber am Ende wirkt es wie eine Neuerfindung von JSON in Binärform
    JSON in V8 ist inzwischen bereits stark optimiert, und Implementierungen wie simdjson schaffen Durchsatz im Gigabyte-Bereich pro Sekunde
    Ich halte es für unwahrscheinlich, dass JSON der Engpass ist

  • Mir gefiel das Design dieses Blogs wirklich sehr
    Besonders gut fand ich die „scrollspy“-Sidebar, die Überschriften je nach Scroll-Position hervorhebt
    Laut Claude wurde die Seite wohl mit fumadocs.dev gebaut

    • Interessant. Ich denke auch, dass ich bald einmal eine gute Doku-Website bauen sollte
  • Ich habe den Zweck des Rust-WASM-Parsers nicht richtig verstanden
    Im Artikel war das nicht klar genug, da bräuchte es mehr Erklärung

    • Offenbar wird eine spezielle Sprache verwendet, um von einem LLM erzeugte UI-Komponenten zu definieren
      Das scheint dazu zu dienen, Informationsabfluss durch Prompt Injection zu verhindern
      Der Parser kompiliert aus dem LLM gestreamte Chunks und baut damit in Echtzeit die UI auf
      Früher wurde der Parser für jeden Chunk wieder von vorne gestartet, aber durch die Umstellung auf inkrementelle Verarbeitung (während der Portierung von Rust nach TypeScript) wurde die Performance stark verbessert
  • Ich hatte die Frage, ob TypeScript inzwischen nicht Go-basiert läuft

    • Es gibt ein Projekt, den TypeScript-Compiler in Go neu zu schreiben; vermutlich ist das gemeint
  • Halb im Scherz: Wenn man es wieder in Rust neu schreibt, gibt es vielleicht noch einmal eine 3-fache Leistungssteigerung /s