Hacker News, TypeScript가 Rust WASM을 이긴 사례에 주목

놀라운 결과의 핵심은 language speed가 아니라 data movement였다

OpenUI의 March 13, 2026 engineering note는 왜 openui-lang parser가 Rust compiled to WASM에서 TypeScript로 완전히 옮겨간 뒤 더 빨라졌는지 설명한다. 이 parser는 LLM이 내보내는 custom DSL을 React component tree로 바꾸고, streaming chunk가 들어올 때마다 반복 실행된다. 그래서 중요한 것은 parser 자체의 순수 처리 속도보다 browser 안에서의 end-to-end latency였다.

팀은 pipeline을 autocloser -> lexer -> splitter -> parser -> resolver -> mapper -> ParseResult로 나눠서 병목을 측정했다. 결론은 Rust parsing speed가 문제가 아니었다는 점이다. 비싼 부분은 매 호출마다 JavaScript와 WASM boundary를 넘는 과정이었다. 입력 string을 WASM memory로 복사하고, 결과를 Rust 안에서 JSON으로 serialize한 뒤, 다시 JSON string을 JS로 복사하고, V8에서 한 번 더 deserialize해야 했다. parser logic이 충분히 빠른 상황에서도 고정된 interop tax를 계속 내고 있었던 셈이다.

직관적인 최적화가 왜 실패했나

OpenUI는 JSON round-trip을 없애기 위해 serde-wasm-bindgen으로 JavaScript object를 직접 반환하는 방법도 시험했다. 하지만 fixture에 따라 9%에서 29% 더 느렸다. 설명은 browser engineer에게 꽤 중요하다. Rust struct를 살아 있는 JavaScript object로 바꾸려면 runtime boundary를 가로지르는 세밀한 conversion이 많이 필요하고, 반대로 JSON string 한 번 전달하는 방식은 Rust가 한 환경에서 serialize하고 V8가 한 번의 최적화된 pass로 parse할 수 있다.

더 큰 승부는 algorithm에서 갈렸다

parser를 완전히 TypeScript로 옮긴 뒤 one-shot parsing은 샘플 문서 기준 2.2배에서 4.6배 빨라졌다. 하지만 실제 production에서 더 중요한 개선은 streaming algorithm을 고친 부분이다. naive approach는 chunk가 올 때마다 누적된 전체 string을 다시 parse해서, 20-character chunk로 들어오는 1000-character response를 총 약 25,000 character 분량의 parsing work로 키운다. OpenUI는 이를 statement-level incremental caching으로 바꿔, 마지막 미완성 statement만 다시 parse하고 이미 끝난 statement는 cache에 보존했다.

공개된 full-stream 수치가 이 차이를 잘 보여 준다. contact-form fixture는 total parse cost가 316 microseconds에서 122로 떨어졌고, dashboard는 840에서 255로 낮아졌다. 더 넓게 보면 이 글의 교훈은 분명하다. WASM은 media processing이나 cryptography처럼 compute-heavy하고 interop가 적은 작업에는 여전히 유효하지만, streaming AI UI 안에서 structured text를 JavaScript object로 자주 바꾸는 workload에는 오히려 맞지 않을 수 있다.

출처: OpenUI engineering note. Hacker News 토론: item 47461094.