Slide 1

Slide 1 text

静的クラスは遅いことが あるよ 藤枝 和宏 twitter: kfujieda [email protected]

Slide 2

Slide 2 text

DynaJson • https://github.com/fujieda/DynaJson/ • 高速なJSONパーザー • DynamicJson互換 dynamic json = JsonObject.Parse(@"{ ""foo"": ""json"", ""nest"": {""foobar"": true} }"); string a1 = json.foo; // "json" bool a2 = json.nest.foobar; // true

Slide 3

Slide 3 text

速い citm_catalog.json (1.7MB)をパーズ .NET Core 3.1 Ubuntu 18.04 on Azure D2ds_v4 0 5 10 15 20 25 30 35 40 DynaJson Utf8Json Jil Newtonsoft.Json DynamicJson Time (ms) ←lower is better

Slide 4

Slide 4 text

静的クラスを避ける public class JsonParser { private static readonly JsonParser Instance = new JsonParser(); // インスタンスは一つ public static object Parse(TextReader reader, int maxDepth) { return Instance.ParseInternal(reader, maxDepth); // インスタンスメソッドを呼ぶ } private object ParseInternal(TextReader reader, int maxDepth) { // パーザーの本体 } }

Slide 5

Slide 5 text

静的クラスは遅い なぜか遅い 0 2 4 6 8 10 12 14 Static Normal Time (ms) ←lower is better .NET Core 3.1 Ubuntu 18.04 on Azure D2ds_v4

Slide 6

Slide 6 text

ディスアセンブルしてみる .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT JsonParser.ParseJsonInternal() ... mov r9d,[rbp+24] inc r9d mov [rbp+24],r9d inc dword ptr [rbp+28] cmp [rbp+20],r9d jne short M04_L01 ... Total bytes of code 7845 JsonParseStatic.Parse() ... mov r9d,[7FFE540EB864] inc r9d mov [7FFE540EB864],r9d inc dword ptr [7FFE540EB868] cmp [7FFE540EB860],r9d jne short M02_L01 ... Total bytes of code 9701

Slide 7

Slide 7 text

中身の大きなループは遅い • DynaJsonのJsonParserは大きなループ • 中身が全部L1キャッシュに乗らないと遅い 1 2 3 4 5 6 7 8 プログラム L1キャッシュ 1 2 3 4 5 6 7 8 9 9

Slide 8

Slide 8 text

なぜコードが大きいのか インライン展開の多用 case 'n': CheckToken("ull"); value.Type = JsonType.Null; break; private void CheckToken(string s) { Consume(); foreach (var ch in s) { if (ch != _nextChar) throw JsonParserException.ExpectingError($"'{ch}'", _position); Consume(); } } private void Consume() { _bufferIndex++; _position++; if (_available == _bufferIndex) { _bufferIndex = 0; _available = _reader.ReadBlock(_buffer, 0, _buffer.Length); if (_available == 0) { _isEnd = true; _nextChar = '¥0'; return; } } _nextChar = _buffer[_bufferIndex]; }

Slide 9

Slide 9 text

なぜコードが大きいのか インライン展開の多用 case 'n': CheckToken("ull"); value.Type = JsonType.Null; break; cmp eax,6E ← 'n' jne near ptr M04_L104 mov r10d,[rbp+24] inc r10d mov [rbp+24],r10d inc dword ptr [rbp+28] cmp [rbp+20],r10d jne near ptr M04_L38 xor r9d,r9d mov [rbp+24],r9d mov rdx,[rbp+10] mov r9d,[rdx+8] mov rcx,[rbp+8] xor r8d,r8d mov rax,[rcx] mov rax,[rax+48] call qword ptr [rax+28] mov [rbp+20],eax cmp dword ptr [rbp+20],0 jne near ptr M04_L38 mov byte ptr [rbp+2E],1 mov word ptr [rbp+2C],0 M04_L08: mov r9,23E10009B48 mov rsi,[r9] xor edi,edi mov r12d,[rsi+8] test r12d,r12d jle short M04_L11 M04_L11: mov dword ptr [rsp+0B4],0FFF80001 jmp near ptr M04_L05 127 bytes

Slide 10

Slide 10 text

インライン展開をやめてみる • 差が縮まった • 静的クラスのキャッシュミスが減ったかな 0 2 4 6 8 10 12 14 16 Static (inlined) Normal (inlined) Static Normal Time (ms) ←lower is better

Slide 11

Slide 11 text

まとめ • 静的クラスはコードが大きくなることがある • 中身の大きすぎるループは遅くなることがある