Rust製のWASMパーサーをTypeScriptで書き直したら、速度が向上しました
概要
- Rust製WASMパーサは、JSとのデータ受け渡しコストがボトルネック
- serde-wasm-bindgenによる直接オブジェクト返却はJSON経由より遅い
- TypeScript移植+インクリメンタルパースで劇的な高速化を実現
- WASMは計算量が大きく、境界をまたぐ回数が少ない用途で最適
- アルゴリズム改善が言語最適化より実効効果大
Rust製WASMパーサの構造と課題
- openui-lang parserはRustで実装、WASMへコンパイル
- LLMが出力する独自DSLをReactコンポーネントツリーへ変換
- 6段階パイプライン構成
- autocloser:途中テキストの構文補完
- lexer:トークン化
- splitter:id=式単位で分割
- parser:AST生成
- resolver:変数参照解決・循環検出
- mapper:ASTをReact用OutputNode形式へ変換
- 各パース呼び出しで必ず発生するJS↔WASM間のデータコピーが遅延要因
JSON経由とserde-wasm-bindgenの比較
- 従来:**serde_json::to_string()**でRust側でJSON化→JSでパース
- 大きな文字列1回のコピー+V8の最適化済みJSON.parse
- serde-wasm-bindgen:Rust構造体をJsValueへ直変換し返却
- しかしJSとRustのメモリレイアウトが異なり、フィールド毎に逐次変換が必要
- 小さなデータを大量に変換するため30%遅くなるケースが多発
ベンチマーク比較(1000回実行、1回あたりの平均μs)
| Fixture | JSON往復 | serde-wasm-bindgen | 差分 | |----------------|----------|--------------------|------------| | simple-table | 20.5 | 22.5 | 9%遅い | | contact-form | 61.4 | 79.4 | 29%遅い | | dashboard | 57.9 | 74.0 | 28%遅い |
TypeScriptへの全面移植とさらなる最適化
- TypeScript移植:同じ6段階構成、WASM排除でV8ヒープ内で完結
- ワンショットパース(1回のparse呼び出し)
| Fixture | TypeScript | WASM | 高速化倍率 | |----------------|------------|------|------------| | simple-table | 9.3 | 20.5 | 2.2x | | contact-form | 13.4 | 61.4 | 4.6x | | dashboard | 19.4 | 57.9 | 3.0x |
ストリーミング時の非効率性とインクリメンタルパース
- LLMのストリーム出力ごとに全体再パース(O(N²))が発生
- 例:1000文字を20文字ずつ50回→累積25,000文字分パース
- 解決策:文単位インクリメンタルキャッシュ
- 完了した文ASTはキャッシュ、未完了分のみ再パース(O(N))
- 完了文は再パース不要、進行中のみ都度パース
ストリーム全体のコスト比較(全チャンク合計μs)
| Fixture | Naïve TS | Incremental TS | 高速化倍率 | |----------------|----------|---------------|------------| | simple-table | 69 | 69 | 変化なし | | contact-form | 316 | 122 | 2.6x | | dashboard | 840 | 255 | 3.3x |
WASM活用の適正領域と教訓
- WASMが向くケース
- 計算集約型でインターフェース回数が少ない処理(画像処理、暗号、物理シミュレーション等)
- 既存C/C++ライブラリのブラウザ移植(例:SQLite, OpenCV)
- WASMが不向きなケース
- 構造化テキストをJSオブジェクト化する処理(パース自体は高速、データ転送が律速)
- 小さな入力に対し頻繁に呼ばれる関数(境界コストが打ち消す)
- 重要な知見
- serde-wasm-bindgenの「直接オブジェクト返却」はコスト削減にならない
- アルゴリズム改善(O(N²)→O(N))が言語最適化より効果大
- WASMとJSはヒープ共有不可、常に変換コストが発生
まとめ
- WASM活用時は「どこで何に時間がかかるか」事前プロファイルが必須
- 境界コストが支配的な用途ではTypeScriptの方が有利
- アルゴリズム設計の見直しが最大の高速化要因