11 points par GN⁺ 2026-03-21 | Aucun commentaire pour le moment. | 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

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.