プロジェクト編集
サイトを開く →
基本情報
タイトル
*
スラッグ
*
変更するとURLが変わります。
ステータス
下書き
公開
限定
アーカイブ
日付
コンテンツ
概要
WASMでjs-yamlの高速版の開発
本文 (HTML)
コンテンツ (Markdown / R2)
## 概要 yaml-gearはRustで作られたWebAssemblyで動作するYAMLパーサーです --- ## 機能 - `parse`:YAML文字列をJSオブジェクトに変換 - `stringify`:JSオブジェクトをYAML文字列に変換 **対応しているYAML** | 機能 | 対応 | |---|---| | Mapping (`key: value`) | ✅ | | Sequence (`- item`) | ✅ | | String / Number / Boolean / Null | ✅ | | シングル・ダブルクォート文字列 | ✅ | | ネスト(最大32段) | ✅ | | アンカー・エイリアス | ❌ | | 複数ドキュメント (`---`) | ❌ | --- ## 開発過程 ### 1. 設計 まずフォルダ構成とAPIの設計を固めました。公開APIは `parse` / `stringify` の関数ベースとし、`JSON.parse` / `JSON.stringify` に近い使い勝手を意識しました。エラーは `throw` で返す設計にして、JS開発者に馴染みやすい形にしています。 内部はLexer・Parser・Emitterの3層に分けています。 ``` YAML文字列 → Lexer → トークン列 → Parser → YamlValue → JsValue JsValue → Emitter → YamlValue → YAML文字列 ``` --- ### 2. Lexerの実装 文字列をトークンに分割する処理です。`:`・`-`・クォート文字列・インデントなどをひとつずつ認識してトークンとして返します。クォート文字列はシングルとダブルで挙動が異なり(シングルはエスケープなし、ダブルは `\n` などが有効)、それぞれ別のトークン型として扱っています。 --- ### 3. Parserの実装 トークン列をYamlValueというRustの型に変換する処理です。Mapping・Sequence・スカラーを再帰的にパースし、インデントの深さで構造を判断します。型変換(`"true"` → Bool、`"42"` → Int など)もここで行っています。 --- ### 4. Emitterの実装 `stringify` 側の処理です。JSオブジェクトをYamlValueに変換してからYAML文字列として出力します。予約語(`true`・`null` など)はそのまま出力するとYAMLとして誤解されるためシングルクォートで囲む処理も入れています。 --- ### 5. ベンチマーク・最適化 js-yamlとのベンチマーク比較を行い、以下の最適化を試みました。 | 施策 | 結果 | |---|---| | `Scalar(String)` → `Scalar(&str)` でコピー削減 | 微改善 | | Lexerをイテレータ化して中間Vecを削減 | ほぼ変化なし | | `serde-wasm-bindgen` で変換を高速化 | 悪化、手動実装に戻した | **最終的なベンチマーク結果** | | yaml-gear | js-yaml | |---|---|---| | parse(大きいYAML) | 496ms | 435ms | | **stringify(大きいYAML)** | **397ms** ✅ | 516ms | | **parse→stringify(大きいYAML)** | **921ms** ✅ | 962ms | データが大きくなるほどWASMの強みが出る結果になりました。小さいとWASMの読み込みなどが重く、そのまま扱える `js-yaml` に負ける形になりました。 --- ## 苦労した点・学び ### Rustのライフタイム トークンを文字列コピーなしで持ち回すために `Scalar(&'a str)` というライフタイム付きの参照を使いました。これをLexer・SpannedToken・Parserまで伝播させる必要があり、コンパイルエラーと何度も格闘しました。最終的にライフタイムの考え方が自分の中で整理できた部分です。 ### WASMの境界コスト JSからWASMに文字列を渡す際にUTF-16からUTF-8への変換が必ず発生します。そのためparseはjs-yamlに対して構造的に不利です。一方でstringifyやラウンドトリップ処理はWASMが有利で、データが大きくなるほど差が出ることを実測で確認しました。 ### 便利なライブラリが必ずしも速いとは限らない `serde-wasm-bindgen` を使えば型変換が楽になる、最適化されたパースが扱えると思って導入しましたが、ベンチマークを取ると手動実装より遅くなっていました。おそらく `serde-wasm-bindgen` が大きめなので、WASMの読み込みで重く、過剰なYAMLパースに対応しているかと... --- ## 今後の展望 - Node.js対応(現在はブラウザのみ) - アンカー・エイリアスのサポート - 複数ドキュメント(`---` 区切り)のサポート - parse速度のさらなる改善
メディア・リンク
サムネイル
GitHub URL
デモ URL
メタ情報
キーワード
タグ
言語
Rust
兄弟プロジェクト (Siblings)
関連するプロジェクトのスラッグとラベルを登録します。
行を追加
← 一覧に戻る
保存
危険ゾーン
このプロジェクトを削除