Rust WASM 파서(Parser)를 TypeScript로 변경, 2~4배 성능 향상!

by DD
2개월 전
조회수 16

Rust로 작성된 WASM 파서(Parser)를 TypeScript로 재작성하여 2~4배의 성능 향상을 달성함

WASM-JS 경계(Boundary)에서의 데이터 전송 오버헤드(Overhead)가 주요 병목 지점이었음

O(N²) 알고리즘을 O(N)으로 개선하여 스트리밍 환경(Streaming Environment)에서의 성능을 극대화함

WASM의 적합한 사용 사례(Use Cases)에 대한 새로운 시각을 제시함

WASM-JS 경계(Boundary) 오버헤드(Overhead) 분석

WASM 파서(Parser)의 성능 저하는 WASM과 JavaScript 간의 데이터 전송 비용(Data Transfer Cost)에서 기인했다. 특히, Rust에서 생성된 파싱 결과를 JSON 문자열로 직렬화(Serialization)하고, 이를 다시 JavaScript에서 역직렬화(Deserialization)하는 과정에서 상당한 오버헤드가 발생했다. JSON 직렬화(Serialization)는 Rust 내부에서 빠르게 처리되지만, 경계를 넘나드는 과정에서 메모리 복사(Memory Copy)V8 엔진(Engine)의 추가적인 작업이 필요했다.

serde-wasm-bindgen의 성능 문제

저자는 `serde-wasm-bindgen`을 사용하여 WASM에서 JavaScript 객체를 직접 반환하는 방식을 시도했지만, 오히려 성능이 저하되었다고 밝혔다. 이는 `serde-wasm-bindgen`이 Rust 데이터를 JavaScript 객체로 변환하기 위해 다수의 경계 교차(Boundary Crossing)를 필요로 하기 때문이다. 반면, JSON 방식은 단일 문자열 복사(String Copy)를 통해 V8의 최적화된 JSON 파싱을 활용하므로, 전반적인 성능(Overall Performance)이 더 우수했다.

알고리즘 복잡도(Algorithmic Complexity) 개선의 중요성

WASM-JS 경계(Boundary) 문제를 해결하는 것보다 알고리즘 복잡도(Algorithmic Complexity)를 개선하는 것이 더 큰 성능 향상을 가져왔다. 특히, O(N²)의 스트리밍 파싱 알고리즘을 O(N)으로 개선하여 스트리밍 환경(Streaming Environment)에서의 성능을 3배 이상 향상시켰다. 이는 캐싱(Caching)을 통해 중복 파싱을 줄이고, 전체 처리 시간을 단축한 결과이다.

WASM의 적합한 사용 사례(Use Cases) 재조명

저자는 WASM의 적합한 사용 사례(Use Cases)를 재정의하며, 계산 집약적(Compute-bound) 작업, 특히 이미지/비디오 처리, 암호화, 물리 시뮬레이션, 오디오 코덱 등에서 WASM의 강점을 강조했다. 또한, WASM과 JavaScript 간의 상호 작용(Interop)이 최소화되는 경우, WASM의 성능 이점을 극대화할 수 있다고 설명했다. 반면, 텍스트 파싱과 같이 잦은 상호 작용(Frequent Interop)이 필요한 경우에는 WASM의 이점이 줄어든다고 지적했다.

SharedArrayBuffers를 활용한 메모리 공유

댓글에서는 WASM과 JavaScript 간의 데이터 전송 오버헤드를 줄이기 위한 대안으로 SharedArrayBuffers를 활용한 메모리 공유 방식을 제안했다. SharedArrayBuffers를 사용하면 데이터 복사 없이 메모리를 공유할 수 있지만, JavaScript 타입의 이점을 잃고, 원시 바이트(Raw Bytes)로 작업해야 하는 단점이 있다. 또한, 이벤트 루프(Event Loop)로 인한 지연 시간(Latency)이 발생할 수 있다는 점도 언급되었다.

We rewrote our Rust WASM parser in TypeScript and it got faster