Este artículo de OpenUI relata la experiencia de migrar un parser de lenguaje de marcado (openui-lang) de Rust compilado a WebAssembly (WASM) a TypeScript, revelando lecciones valiosas sobre optimización de rendimiento y la importancia de perfilar el código antes de tomar decisiones de implementación. Inicialmente, el equipo optó por Rust y WASM por su velocidad, asumiendo que la rapidez de Rust compensaría la latencia introducida por WASM. El parser, crucial para convertir un DSL generado por un LLM en un árbol de componentes React, se ejecuta en cada fragmento de datos transmitidos, haciendo que la latencia sea crítica.
El proceso de parsing se divide en seis etapas: autocierre, lexer, splitter, parser, resolver y mapper. La sorpresa llegó al descubrir que la lentitud no residía en el código Rust en sí, sino en la sobrecarga de la frontera entre WASM y JavaScript. Cada llamada al parser WASM implicaba copiar datos de cadena hacia WASM, serializar el resultado a JSON, copiar el JSON de vuelta a JavaScript y, finalmente, deserializarlo. Intentaron optimizar esto utilizando serde-wasm-bindgen para retornar un objeto JavaScript directamente desde WASM, pero esto resultó ser un 30% más lento debido a la complejidad de la conversión entre la memoria de Rust y la de JavaScript.
La solución definitiva fue portar todo el pipeline de parsing a TypeScript, eliminando por completo la frontera WASM. Esto resultó en una mejora significativa del rendimiento: 2.2x a 4.6x más rápido por llamada y una reducción de 2.6x a 3.3x en el costo total de procesamiento en streaming. Además, se implementó un sistema de caché incremental a nivel de declaración para optimizar aún más el rendimiento en streaming, pasando de una complejidad O(N²) a O(N).
La experiencia demostró que WASM es más adecuado para tareas intensivas en cómputo con poca interacción con JavaScript, como procesamiento de imágenes o bibliotecas nativas portables. Para tareas como el parsing de texto estructurado en objetos JavaScript, el costo de la frontera entre WASM y JavaScript puede superar cualquier beneficio de rendimiento de un lenguaje de bajo nivel como Rust. El artículo concluye enfatizando la importancia de perfilar el código para identificar los cuellos de botella reales antes de tomar decisiones sobre el lenguaje de programación y la tecnología a utilizar.
