オープンソース
Capcom RE:Dox、ゲームエンジン向けのオープンソースシリアライゼーション/デシリアライゼーション
Capcom RE:Dox, open source serialization/deserialization for game engines (github.com)
要約
Capcomが開発したRE:Doxは、.NET向けの高性能トークンベース構造化データエンジンです。JSON、CBOR、MessagePack、TOML、XMLなど多様なフォーマットに対応し、単なるシリアライザではなく、構造化データをコンパクトなトークンDOM/IRに解析し、それを基に読み込み、編集、フォーマット変換、シリアライズ、デシリアライズ、自動並列デシリアライズを行います。これにより、従来のシリアライザが持つ速度、編集性、柔軟性のトレードオフを克服し、高速かつ低アロケーションなデータ処理を実現します。
全文翻訳
RE:Dox
1. RE:Doxとは何か?
RE:Doxは、CAPCOMの次世代ゲームエンジン「REX」テクノロジーのコアコンポーネントの一つとして開発された、.NET向けの高性能トークンベース構造化データエンジンです。
RE:Doxは単なるJSONシリアライザではありません。構造化データをコンパクトで固定サイズのトークンDOM / IRに解析し、その構造インデックスを使用して読み込み、編集、フォーマット変換、シリアライズ、デシリアライズ、自動並列デシリアライズを行います。
JSON / JSON5 / CBOR / MessagePack / TOML / XML / HTML / CSV / INI / DOX
↓
コンパクトなトークンDOM / IR
↓
Reader / Writer / Serializer / Deserializer
↓
.NETオブジェクト、JSON、CBOR、MessagePack、TOML、XML、HTML、DOX、...
同じトークン表現は、コンパクトな解析済みドキュメントモデルと、シリアライザ、コンバータ、サポートされているフォーマット間で共有される共通の構造レイヤーの両方として機能します。
2. なぜ気にかけるべきか?
RE:Doxは、通常は相反する3つの目標を組み合わせています。テープDOM (System.Text.Json.JsonDocument) の解析速度、ノードDOM (System.Text.Json.Nodes.JsonNode) の編集性、Newtonsoft.Jsonの柔軟性 (位置に依存しない $type, $id / $ref、順不同のコンストラクタバインディング)。
得られるもの:
高性能なシリアライズとデシリアライズ — ベンチマークデータセットでは、System.Text.Jsonと比較して最大約1.8倍高速なシーケンシャルデシリアライズ、最大約2.8倍高速な自動並列デシリアライズ、最大約1.6倍高速なシリアライズを実現します。
テストされたワークロードでのアロケーション削減 — 例えば、canada.jsonのデシリアライズでは、RE:Doxは約2.56 MB、System.Text.Jsonは約8.53 MBをアロケーションします。
JSON、JSON5、CBOR、MessagePack、INI、DOXはコアモデルに統合されています。TOML、XML、HTML、CSVは現在プレビューコンポーネントです。
フォーマットを横断する単一のコンバータモデル — DataConverter<T>は、特定のワイヤーフォーマットではなく、フォーマットに依存しないDataReader / DataWriter抽象化をターゲットとしています。
ミュータブルなトークンDOM — 重いマネージドオブジェクトツリーでドキュメントを置き換えることなく、オブジェクトと配列を編集できます。
トリビアを保持するJSON5編集 — コメントやその他の保持されたトリビアは、ドキュメントの編集と再エンコード後も存続できます。
System.Text.Json、Newtonsoft.Json、DataContractJsonSerializerとの互換性レイヤー。
Apache-2.0ライセンス。
3. 30秒の例
コアパッケージをインストールします:
dotnet add package CAPCOM.REDox
同じAPIサーフェスを通じて、シリアライズ、デシリアライズ、パース、編集を行います:
using REDox.Json;
var player = new Player { Name = "Leon", Level = 42, Items = ["Handgun", "Green Herb"] };
// シリアライズ / デシリアライズ
var json = JsonSerializer.Serialize(player);
var restored = JsonSerializer.Deserialize<Player>(json);
// トークンDOMにパースしてインプレースで編集します
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement.AsObject();
root["Name"] = "Claire"; // 置換
root.Add("Hp", 100); // 追加
root.Remove("Level"); // 削除
var items = root["Items"].AsArray();
items.Add("First Aid Spray"); // 追加
items.Insert(0, "Knife"); // 先頭に挿入
items.RemoveAt(1); // インデックスで削除
var edited = doc.RootElement.ToJsonString();
public sealed class Player {
public string? Name { get; set; }
public int Level { get; set; }
public string[] Items { get; set; } = [];
}
4. パフォーマンス
環境: BenchmarkDotNet, .NET 10 (X64 RyuJIT x86-64-v3), AMD Ryzen Threadripper PRO 5975WX, Windows 11。
概要: System.Text.Jsonに対する速度向上
| Dataset | Deserialize | Deserialize (parallel) | Serialize |
|---------------|-------------|------------------------|-----------|
| canada.json | 1.68x | 2.84x | 1.06x |
| citm_catalog.json | 1.77x | 2.25x | 1.62x |
| twitter.json | 1.36x | 2.34x | 1.42x |
これらの比率は以下のベンチマークデータセットに対するものであり、普遍的なパフォーマンス保証ではありません。
デシリアライズ: canada.json (数値中心、約2.2 MB)
| Dataset | Mean / Allocated (RE:Dox) | Mean / Allocated (RE:Dox (parallel)) | Mean / Allocated (System.Text.Json) | Mean / Allocated (STJ (JsonTypeInfo)) | Mean / Allocated (Utf8Json) |
|---------------|-------------------------|------------------------------------|-------------------------------------|---------------------------------------|-----------------------------|
| canada.json | 9,113.0 us / 2,619.92 KB | 5,384.6 us / 2,685.11 KB | 15,303.1 us / 8,734.66 KB | 15,005.9 us / 8,734.59 KB | 15,864.0 us / 6,655.67 KB |
| citm_catalog.json | 2,280.5 us / 553.55 KB | 1,795.8 us / 631.87 KB | 4,041.1 us / 1,175.38 KB | 3,940.8 us / 1,175.38 KB | 2,605.5 us / 1,124.13 KB |
| twitter.json | 1,008.8 us / 504.91 KB | 587.4 us / 555.84 KB | 1,371.6 us / 557.09 KB | 1,401.6 us / 557.09 KB | 1,370.6 us / 530.05 KB |
シリアライズ: Mean / Allocated
| Dataset | RE:Dox | System.Text.Json | STJ (JsonTypeInfo) | Utf8Json |
|---------------|-------------------------|-----------------------------------|-----------------------------------|-----------------------------|
| canada.json | 13,615.5 us / 2,041.41 KB | 14,388.3 us / 2,042.85 KB | 14,433.1 us / 2,042.85 KB | 11,714.2 us / 6,042.21 KB |
| citm_catalog.json | 668.7 us / 494.41 KB | 1,085.4 us / 494.85 KB | 1,073.8 us / 494.87 KB | 707.3 us / 1,389.64 KB |
| twitter.json | 557.3 us / 465.15 KB | 789.0 us / 467.95 KB | 800.3 us / 467.91 KB | 709.4 us / 1,361.37 KB |
再現方法: dotnet run -c Release --project benchmarks/REDox.Json.Benchmarks
ベンチマーク結果は、データ形状、ターゲット型、ランタイム、CPU、シリアライザオプションによって異なります。常に独自のワークロードでベンチマークを行ってください。
5. アーキテクチャ
コアにある64ビットデュアルモードトークン
すべての値は固定サイズの64ビットトークンによって記述されます。拡張ビットは、ペイロードの解釈方法を選択します。
拡張ビット = 0 → ペイロードの解釈はドキュメント/フォーマットレイヤーに委任されます(ソースベース、フォーマット固有のペイロード。多くのフォーマット、1つのトークン形状)。
拡張ビット = 1 → ペイロードの解釈はRE:Doxによって固定され、制御トークンがフォーマットに依存しない編集(挿入/削除/置換)を可能にします。
これにより、1つのトークン構造が、フォーマットデータのソースベースのビューと、シリアライザ、コンバータ、サポートされているフォーマット間で共有される共通の構造レイヤーの両方として機能します。
ドキュメント/フォーマットレイヤーが元のペイロードを保持できる場合、変更されていない値は、早期にマテリアライズされることなく、ソーススライスを再利用し続けることができます。
トークンは、null、ブール値、整数、浮動小数点数、文字列、バイナリ、タイムスタンプ、大きな数値、配列、マップ/オブジェクト、トリビア(コメント/空白)、またはフォーマット固有の拡張データを表すことができます。インラインデータ、ソースオフセット/長さ、コンテナ数、リンク情報、または拡張IDを格納できます。
非対称な読み書き設計
読み込みと書き込みでは異なる情報要件があるため、RE:Doxはそれぞれ異なるパスを使用します。
シリアライズ: DataWriter、1パス、直接出力バッファに書き込まれます(情報は既知 → 先読みなし、中間DOMなし)。
デシリアライズ: トークンDOM上のDataReader、トークンIDによるランダムアクセス(情報は不明 → 先読み、順不同、コンテキストルックアップ)。
書き込みパスでは値は既にわかっているため、RE:Doxはバルク (WriteValues) およびフューズドプロパティ (WriteProperty*) ヘルパーを使用して直接バッファに書き込みます。中間オブジェクトツリーは構築されません。
読み込みパスでは構造が不明なため、RE:DoxはまずコンパクトなトークンDOMを構築します。構造が完全にアドレス可能になったため、RE:Doxは以下を実行できます。
配列とコレクションの事前サイズ設定。
マテリアライズする前に各要素のトークンIDを知る。
$type をオブジェクト内のどこにあっても解決する。
コンストラクタ引数を順不同で収集し、一度だけバインドする(レコードとプライマリコンストラクタ)。
ドキュメント全体のコンテキストを使用して $id / $ref サイクルを解決する。
シーケンシャルまたはパラレルデシリアライズを自動的に選択する。
要求されたときにのみ文字列と数値をデコードする。
変更されていない値に対して、生のソーススライスを再利用する。
遅延デコードとオンデマンドマテリアライゼーション
トークンは、元のソースバッファへのオフセットと長さを格納できます。文字列、数値、タイムスタンプ、バイナリ値は、要求されたときにのみデコードされます。要素はドキュメントとトークンID上の軽量ハンドルであり、オブジェクトと配列ビューは必要になったときにのみマテリアライズされます。
ミュータブルなテープDOM
テープDOMは通常読み取り専用ですが、その論理構造はフラットなトークンシーケンスによって表されます。RE:Doxはコンテナビューに間接参照を追加します。DArray、DObject、DMapは、再リンク可能な値スロットを保持し、フリーリストは空になったトークンスロットを再利用し、拡張/制御トークンは編集状態を運びます。これにより、シーケンシャルなトークン表現のキャッシュフレンドリーな特性を維持しながら、ドキュメント全体を編集ごとに再構築することなく、挿入、削除、置換などのノードライクな操作を提供できます。
ユニファイドコンバータ
コンバータ (DataConverter<T>) は、仕様に直接対するのではなく、フォーマットに依存しないDataReader / DataWriter抽象化に対して記述されます。