11 points par GN⁺ 2026-03-21 | 2 commentaires | Partager sur WhatsApp
  • Le parseur WASM écrit en Rust est structurellement rapide, mais la copie des données et le surcoût de sérialisation à la frontière JS-WASM se sont révélés être le goulot d’étranglement des performances
  • Le retour direct d’objets via serde-wasm-bindgen s’est avéré 9 à 29 % plus lent que la sérialisation JSON, en raison du coût des conversions fines entre runtimes
  • En portant tout le pipeline en TypeScript, les mêmes choix d’architecture ont permis d’obtenir des performances par appel 2,2 à 4,6 fois supérieures
  • En traitement de flux, le passage de O(N²) à O(N) grâce à un cache incrémental par phrase a permis d’obtenir une vitesse de traitement globale 2,6 à 3,3 fois supérieure
  • En conséquence, il apparaît que WASM convient aux traitements intensifs en calcul et peu fréquents, mais pas au parsing d’objets JS ni aux fonctions appelées très souvent

Structure et limites du parseur Rust WASM

  • Le parseur openui-lang est constitué d’un pipeline en 6 étapes qui convertit un DSL généré par un LLM en arbre de composants React
    • Étapes : autocloser → lexer → splitter → parser → resolver → mapper → ParseResult
    • Chaque étape effectue la tokenisation, l’analyse syntaxique, la résolution des variables, la transformation d’AST, etc.
  • Le code Rust lui-même est rapide, mais les opérations de copie de chaînes, sérialisation JSON et désérialisation entre JS↔WASM se produisent à chaque appel
    • Copie de la chaîne d’entrée (JS→WASM), parsing côté Rust, sérialisation du résultat en JSON, copie du JSON (WASM→JS), puis désérialisation côté JS
  • Ce surcoût à la frontière dominait les performances globales, la vitesse de calcul de Rust n’étant pas le goulot d’étranglement

Tentative avec serde-wasm-bindgen et échec

  • Pour éviter la sérialisation JSON, serde-wasm-bindgen, qui retourne directement des structures Rust sous forme d’objets JS, a été appliqué
  • Mais on a observé une baisse de performance de 30 %
    • JS ne peut pas lire directement la mémoire des structures Rust, et comme la disposition mémoire diffère entre runtimes, une conversion champ par champ est nécessaire
    • À l’inverse, la sérialisation JSON consiste à générer une chaîne une seule fois côté Rust, puis à la traiter côté JS via un JSON.parse optimisé
  • Résultats du benchmark
    Fixture Aller-retour JSON serde-wasm-bindgen Variation
    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%

Passage à TypeScript et amélioration des performances

  • Le même pipeline en 6 étapes a été entièrement porté en TypeScript, supprimant la frontière WASM pour s’exécuter directement dans le heap de V8
  • Résultats du benchmark par appel
    Fixture TypeScript WASM Gain de vitesse
    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
  • La simple suppression de WASM a fortement réduit le coût par appel, mais l’inefficacité de la structure de streaming restait présente

Le problème en O(N²) du parsing en flux et son amélioration

  • Lorsque la sortie du LLM est livrée en plusieurs chunks, le fait de reparser à chaque fois toute la chaîne accumulée introduit une inefficacité en O(N²)
    • Exemple : parser un document de 1000 caractères 50 fois par blocs de 20 caractères revient à traiter 25 000 caractères au total
  • La solution a consisté à introduire un cache incrémental par phrase
    • Les phrases complètes sont mises en cache, et seule la phrase en cours est reparsée
    • L’AST en cache est fusionné avec le nouvel AST avant de retourner le résultat
  • Benchmarks sur l’ensemble du flux
    Fixture TS naïf TS incrémental Gain de vitesse
    simple-table 69µs 77µs Aucun
    contact-form 316µs 122µs 2.6x
    dashboard 840µs 255µs 3.3x
  • Plus il y a de phrases, plus l’effet du cache augmente, et le débit global s’améliore linéairement

Enseignements sur l’usage de WASM

  • Cas adaptés
    • Travaux intensifs en calcul avec peu d’interactions : traitement d’image et de vidéo, chiffrement, simulation physique, codecs audio, etc.
    • Portage de bibliothèques natives existantes : SQLite, OpenCV, libpng, etc.
  • Cas inadaptés
    • Parsing de texte structuré en objets JS : le coût de sérialisation devient dominant
    • Fonctions appelées fréquemment sur des entrées courtes : le coût de frontière dépasse celui du calcul
  • Principaux enseignements
    1. Il faut profiler les goulots d’étranglement avant de choisir le langage
    2. Le passage direct d’objets avec serde-wasm-bindgen coûte plus cher
    3. Améliorer la complexité algorithmique est plus efficace qu’un changement de langage
    4. WASM et JS ne partagent pas le même heap, et le coût de conversion existe toujours

Résultat final : le passage à TypeScript et le cache incrémental ont permis d’obtenir des performances 2,2 à 4,6 fois supérieures par appel, et 2,6 à 3,3 fois supérieures sur l’ensemble du flux

2 commentaires

 
bbulbum 2026-03-23

N’était-ce pas, au fond, une façon détournée de se moquer d’un article très pointu sur l’optimisation des performances en Rust..

 
GN⁺ 2026-03-21
Commentaires sur Hacker News
  • Le vrai point clé, ce n’est pas TypeScript plutôt que Rust, mais la correction de l’algorithme de streaming, passé de O(N²) à O(N)
    Rien que ce changement, avec une mise en cache au niveau des instructions (statement), a apporté un gain de 3,3×
    Indépendamment du choix du langage, c’est surtout cela qui explique l’amélioration de la latence perçue par les utilisateurs
    On a l’impression que le titre sous-estime ce point d’ingénierie pourtant intéressant

    • Le projet uv, c’est pareil. Les gens crient juste « rust rulez! », alors que le vrai gain vient des améliorations algorithmiques, pas du langage
    • Merci d’avoir percé le clickbait pour pointer l’essentiel
      L’article en lui-même est intéressant, mais je suis fatigué ces temps-ci des titres trop racoleurs
    • La notation n² semble un peu exagérée
      La méthode consiste à mesurer le temps de chaque appel puis à utiliser la médiane (median), mais dans un navigateur, avec les protections contre les attaques par temporisation intégrées aux moteurs JS, j’ai des doutes sur la précision
    • Au final, je pense que c’est plutôt un titre trompeur
  • Dire « on a réécrit du code du langage L vers M et c’est devenu plus rapide » est assez banal
    Parce que cela donne l’occasion de corriger des choix enchevêtrés et erronés, et d’appliquer de meilleures approches apparues entre-temps
    En fait, même si L=M, c’est pareil : les gains de performance viennent moins du langage que du processus de réécriture et de reconception

    • Si un tiers réécrivait maintenant la version TypeScript en Rust sans connaître l’original, les performances monteraient peut-être encore
    • Je vois souvent des améliorations même lors d’une réécriture dans le même langage
  • J’ai creusé davantage pour améliorer les performances de sérialisation d’objets à la frontière entre Rust et JS
    L’approche de serde ne me semblait pas bonne en matière de performances, et j’ai résumé une tentative d’amélioration dans mon billet de blog

  • Je me demandais pourquoi Open UI ne faisait rien autour de WASM
    Mais cette nouvelle entreprise utilise justement le nom Open UI, ce qui m’a embrouillé
    À l’origine, l’Open UI W3C Community Group est un groupe qui travaille depuis plus de 5 ans sur des standards HTML comme popover, les select personnalisables, invoker command ou encore accordion
    Ils font vraiment un excellent travail

  • Ils disent avoir intégré serde-wasm-bindgen dans l’idée d’« éviter l’aller-retour JSON », mais au final cela ressemble à une réinvention de JSON en binaire
    Le JSON de V8 est aujourd’hui déjà très optimisé, et des implémentations comme simdjson peuvent traiter des gigaoctets par seconde
    Il me semble peu probable que JSON soit le goulot d’étranglement

  • J’ai vraiment aimé le design de ce blog
    J’ai particulièrement apprécié la barre latérale de type « scrollspy » qui met en évidence les titres selon la position de défilement
    D’après Claude, cela semble avoir été fait avec fumadocs.dev

    • Intéressant. Je me dis moi aussi qu’il va falloir que je crée bientôt un bon site de documentation
  • Je n’ai pas bien compris l’objectif du parseur Rust WASM
    Ce point n’était pas clair dans l’article et mériterait plus d’explications

    • Apparemment, ils utilisent un langage dédié pour définir des composants d’UI générés par un LLM
      Cela semble viser à empêcher les fuites d’informations dues au prompt injection
      Le parseur compile les morceaux diffusés en streaming par le LLM afin de construire l’UI en temps réel
      Avant, le parseur redémarrait depuis zéro à chaque chunk, puis ils sont passés à une approche incrémentale, ce qui a fortement amélioré les performances (pendant le portage de Rust vers TypeScript)
  • Je me demandais si TypeScript ne tournait pas désormais sur une base Golang

    • Il y a un projet en cours pour réécrire le compilateur TypeScript en Go, c’est probablement à cela qu’il fait référence
  • C’est une blague, mais si on le réécrivait encore une fois en Rust, on obtiendrait peut-être encore 3× plus de performances /s