11 puntos por GN⁺ 2026-03-21 | 2 comentarios | Compartir por WhatsApp
  • El parser WASM escrito en Rust es estructuralmente rápido, pero la copia de datos y la sobrecarga de serialización en el límite entre JS y WASM terminaron siendo el cuello de botella de rendimiento
  • Devolver objetos directamente mediante serde-wasm-bindgen fue entre 9 y 29% más lento que la serialización JSON, debido al costo de conversiones detalladas entre runtimes
  • Al portar todo el pipeline a TypeScript, se logró en la misma arquitectura un rendimiento por llamada entre 2.2 y 4.6 veces mayor
  • En procesamiento por streaming, una mejora de O(N²)→O(N) mediante caché incremental por oración permitió obtener una velocidad total de procesamiento entre 2.6 y 3.3 veces mayor
  • En conclusión, WASM es adecuado para cargas intensivas de cómputo y llamadas poco frecuentes, pero no es adecuado para parseo de objetos JS ni para funciones invocadas con mucha frecuencia

Estructura y límites del parser WASM en Rust

  • El parser openui-lang está compuesto por un pipeline de 6 etapas que convierte el DSL generado por un LLM en un árbol de componentes de React
    • Etapas: autocloser → lexer → splitter → parser → resolver → mapper → ParseResult
    • Cada etapa realiza tokenización, análisis sintáctico, resolución de variables, transformación del AST, etc.
  • El código Rust en sí es rápido, pero en cada llamada ocurre el proceso de copiar cadenas entre JS↔WASM, serializar JSON y deserializarlo
    • Copia de la cadena de entrada (JS→WASM), parseo interno en Rust, serialización del resultado a JSON, copia del JSON (WASM→JS), deserialización en JS
  • Esa sobrecarga en el límite entre ambos entornos terminó dominando el rendimiento total; la velocidad de cómputo de Rust no era el cuello de botella

El intento con serde-wasm-bindgen y por qué falló

  • Para evitar la serialización JSON, se aplicó serde-wasm-bindgen, que devuelve estructuras de Rust directamente como objetos JS
  • Sin embargo, se observó que era 30% más lento
    • JS no puede leer directamente la memoria de una estructura Rust, y como el layout de memoria difiere entre runtimes, se requiere una conversión campo por campo
    • En cambio, la serialización JSON genera una sola cadena dentro de Rust, que luego JS procesa con un JSON.parse altamente optimizado
  • Resultados del benchmark
    Fixture JSON round-trip serde-wasm-bindgen Cambio
    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%

Cambio a TypeScript y mejora de rendimiento

  • Se hizo un port completo a TypeScript de la misma estructura de 6 etapas, eliminando el límite con WASM y ejecutando todo directamente dentro del heap de V8
  • Resultados del benchmark por llamada individual
    Fixture TypeScript WASM Mejora de velocidad
    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
  • Solo con eliminar WASM, el costo por llamada cayó de forma notable, aunque la ineficiencia de la estructura de streaming seguía presente

El problema O(N²) del parseo por streaming y su mejora

  • Cuando la salida del LLM llega en varios chunks, se producía una ineficiencia O(N²) al volver a parsear cada vez toda la cadena acumulada
    • Ejemplo: un documento de 1000 caracteres parseado 50 veces en bloques de 20 caracteres → 25,000 caracteres procesados en total
  • Como solución, se introdujo una caché incremental por oración
    • Las oraciones completas se guardan en caché, y solo se vuelve a parsear la oración en curso
    • El AST cacheado y el AST nuevo se combinan para devolver el resultado
  • Benchmark sobre el stream completo
    Fixture TS ingenuo TS incremental Mejora de velocidad
    simple-table 69µs 77µs Ninguna
    contact-form 316µs 122µs 2.6x
    dashboard 840µs 255µs 3.3x
  • Cuantas más oraciones haya, mayor es el efecto de la caché, y el throughput total mejora de forma lineal

Lecciones sobre el uso de WASM

  • Casos adecuados
    • Tareas intensivas en cómputo y con poca interacción: procesamiento de imagen y video, criptografía, simulación física, códecs de audio, etc.
    • Portabilidad de librerías nativas existentes: SQLite, OpenCV, libpng, etc.
  • Casos no adecuados
    • Parseo de texto estructurado hacia objetos JS: el costo de serialización domina
    • Funciones con entradas cortas y llamadas frecuentes: el costo del límite entre entornos supera al cómputo
  • Lecciones clave
    1. Hay que perfilar dónde está el cuello de botella antes de elegir el lenguaje
    2. El paso directo de objetos con serde-wasm-bindgen es más costoso
    3. Mejorar la complejidad algorítmica puede ser más efectivo que cambiar de lenguaje
    4. WASM y JS no comparten el heap, y el costo de conversión siempre existe

Resultado final: con el cambio a TypeScript y la caché incremental se logró una mejora de rendimiento de 2.2 a 4.6 veces por llamada, y de 2.6 a 3.3 veces en el stream completo

2 comentarios

 
bbulbum 2026-03-23

¿No habrá sido más bien con la intención de tirarle indirectas a un artículo muy técnico sobre optimización de rendimiento en Rust..?

 
GN⁺ 2026-03-21
Comentarios en Hacker News
  • El verdadero punto clave no es TypeScript frente a Rust, sino la corrección del algoritmo de streaming al reducirlo de O(N²) a O(N)
    Solo este cambio, basado en caché a nivel de sentencias (statement), ya dio una mejora de 3.3x
    Independientemente del lenguaje elegido, desde la perspectiva del usuario, el principal factor de mejora en la latencia fue esta parte
    Da la impresión de que el título subestima este interesante punto de ingeniería

    • En el proyecto uv pasa lo mismo. La gente solo grita “rust rulez!”, pero la ganancia real no viene del lenguaje sino de la mejora del algoritmo
    • Gracias por atravesar el clickbait y señalar lo esencial
      El artículo en sí es interesante, pero últimamente ya estoy cansado de los títulos excesivamente diseñados para generar clics
    • La expresión n² parece un poco exagerada
      Se trata de medir el tiempo de cada llamada y usar la mediana (median), pero en navegadores, donde los motores JS tienen defensas contra ataques por temporización, dudo de la precisión
    • Al final, creo que se acerca más a un título engañoso
  • Decir “reescribimos código del lenguaje L a M y se volvió más rápido” es algo esperable
    Porque fue una oportunidad para corregir decisiones enredadas y erróneas, y aplicar un mejor enfoque que apareció después
    De hecho, aunque L=M, la mejora de velocidad suele venir no del lenguaje sino del proceso de reescritura y rediseño

    • Ahora, si un tercero reescribe la versión TypeScript en Rust sin conocer el original, quizá vuelva a mejorar el rendimiento
    • A menudo veo que incluso al reescribir en el mismo lenguaje aparecen mejoras
  • Investigué más a fondo intentando mejorar el rendimiento de la serialización de objetos en la frontera entre Rust y JS
    El enfoque de serde no parecía bueno en términos de rendimiento, y resumí un intento de mejorarlo en mi entrada de blog

  • Me preguntaba por qué Open UI no estaba haciendo trabajo relacionado con WASM
    Pero luego me confundí porque esta nueva empresa usa el nombre Open UI
    El grupo original, Open UI W3C Community Group, lleva más de 5 años creando estándares como popover de HTML, select personalizable, invoker command y accordion
    Realmente están haciendo un gran trabajo

  • Dicen que integraron serde-wasm-bindgen en el intento de “saltarse el viaje de ida y vuelta por JSON”, pero al final parece reinventar JSON en forma binaria
    Hoy en día el JSON de V8 ya está muy optimizado, y implementaciones como simdjson pueden procesar gigabytes por segundo
    No creo que JSON sea el cuello de botella

  • Me gustó muchísimo el diseño de ese blog
    En particular, la barra lateral tipo ‘scrollspy’ que resalta los encabezados según la posición de desplazamiento me pareció genial
    Según me dijo Claude, parece que fue hecho con fumadocs.dev

    • Qué interesante. Yo también creo que pronto debería hacer un buen sitio de documentación
  • No me quedó claro cuál era exactamente el propósito del parser Rust WASM
    Esa parte no estaba clara en el artículo y necesitaba más explicación

    • Al parecer usan un lenguaje dedicado para definir componentes de UI generados por LLM
      Esto parece buscar evitar la filtración de información causada por prompt injection
      El parser compila los fragmentos transmitidos por streaming desde el LLM para construir una UI en tiempo real
      Antes reiniciaban el parser desde cero con cada fragmento, pero al cambiarlo a un método de procesamiento incremental (durante el port de Rust a TypeScript), el rendimiento mejoró mucho
  • Tenía la duda de si TypeScript hoy en día no corre sobre una base de Golang

    • Parece que se refiere al proyecto para reescribir el compilador de TypeScript en Go
  • En broma, pero quizá si lo reescriben otra vez en Rust haya otra mejora de rendimiento de 3x /s