Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NaN BoxingによるJSONパーサーの高速化

NaN BoxingによるJSONパーサーの高速化

DynaJsonの高速化で使っているNaN Boxingという技術を紹介する。

Af61fe2beec7f195d7418e0a474a4dfc?s=128

Kazuhiro Fujieda

February 14, 2020
Tweet

Transcript

  1. NaN BoxingによるJSON パーサーの高速化 藤枝 和宏 twitter: kfujieda fujieda@roundwide.com

  2. DynaJson • https://github.com/fujieda/DynaJson/ • 高速なJSONパーサー • DynamicJson互換 var json =

    JsonObject.Parse(@"{ ""foo"": ""json"", ""nest"": {""foobar"": true} }"); var a1 = json.foo; // "json" var a2 = json.nest.foobar; // true
  3. 速い citm_catalog.json (1.7MB)⇒DynamicObject 11.849 21.123 34.711 50.772 58.141 0 10

    20 30 40 50 60 70 DynaJson Utf8Json Jil Newtonsoft.Json DynamicJson Time (ms) ←lower is better
  4. なぜ速いか • JSONの値を16バイトの構造体に格納 • 動的メモリ割り当てが不要に • 代入でコピーが発生するが16バイトまでは速い

  5. 16バイトに格納する • Explicitレイアウトでフィールドを重ねる • doubleの上4バイトにJsonTypeを重ねる ⇒ NaN Boxing [StructLayout(LayoutKind.Explicit)] internal

    struct InternalObject { [FieldOffset(4)] public JsonType Type; [FieldOffset(0)] public double Number; [FieldOffset(8)] public string String; [FieldOffset(8)] public JsonArray Array; [FieldOffset(8)] public JsonDictionary Dictionary; }
  6. NaN Boxing • doubleのNaNの隙間に値を詰める • NaNは0/0などで発生する特別な値 • 表現は0xfff800000000 (qNANの場合) •

    下位51ビットは任意の値でいい
  7. NaNに値を詰める enum JsonType : uint { Null = 0xfff80001, True

    = 0xfff80002, False = 0xfff80003, String = 0xfff80004, Array = 0xfff80005, Object = 0xfff80006 } static object ToValue(InternalObject obj) { switch (obj.Type) { case JsonType.Null: return null; case JsonType.True: return true; case JsonType.False: return false; case JsonType.String: return obj.String; case JsonType.Array: case JsonType.Object: return new JsonObject(obj); default: return obj.Number; } }
  8. NaN Boxingの効果 11.849 13.418 16.294 0 5 10 15 20

    struct 16 struct 24 class Time (ms) ←lower is better