ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法

8,060 views

Published on

https://github.com/neuecc/ZeroFormatter

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
8,060
On SlideShare
0
From Embeds
0
Number of Embeds
7,572
Actions
Shares
0
Downloads
6
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法

  1. 1. Work C# Unity Private http://neue.cc/ @neuecc https://github.com/neuecc/UniRx
  2. 2. using
  3. 3. ZeroFormatter
  4. 4. 秘訣は...
  5. 5. 評価を遅らせる
  6. 6. 無限大高速なシリアライザ https://github.com/neuecc/ZeroFormatter/ シリアライズも速い
  7. 7. 無限大高速なシリアライザ https://github.com/neuecc/ZeroFormatter/ シリアライズも速い
  8. 8. Benchmark
  9. 9. 本当に速いの?
  10. 10. はい。
  11. 11. 辻ベンチマーク
  12. 12. Serialization Process
  13. 13. 例えばint(999)をbyte[]にシリアライズ var bytes = BitConverter.GetBytes(999); unsafe { var bytes = new byte[4]; fixed (byte* ptr = bytes) { *((int*)ptr) = 999; } }
  14. 14. // ふつーのシリアライザのAPIの例 byte[] Serialize<T>(T obj) { // 1. 内部での書き込みストリーム作りのためにnew MemoryStream using(var stream = new MemoryStream()) // 2. データ生成時の内部ステートを保持するためのWriterのnew var writer = new XxxWriter(stream); // 3. Int用子シリアライザの取得あるいはprimitiveの場合はswitch var serializer = serializerCacheDictionary[typeof(T)]; // 4. (意外と内部では入ってることがある)objectへのボクシング serializer.WriteObject(writer, (object)obj); // 5. 可変長整数へのエンコード if(x <10) write... else if(x < 150) write... // 6. WriteByte呼び出しの連打(内部では幾つかのifやインクリメント) stream.WriteByte(byte >> 0); stream.WriteByte(byte >> 8) ... // 7. MemoryStreamのToArrayはbyte[]コピー memoryStream.ToArray(); }
  15. 15. // ふつーのシリアライザのAPIの例 byte[] Serialize<T>(T obj) { // 1. 内部での書き込みストリーム作りのためにnew MemoryStream using(var stream = new MemoryStream()) // 2. データ生成時の内部ステートを保持するためのWriterのnew var writer = new XxxWriter(stream); // 3. Int用子シリアライザの取得あるいはprimitiveの場合はswitch var serializer = serializerCacheDictionary[typeof(T)]; // 4. (意外と内部では入ってることがある)objectへのボクシング serializer.WriteObject(writer, (object)obj); // 5. 可変長整数へのエンコード if(x <10) write... else if(x < 150) write... // 6. WriteByte呼び出しの連打(内部では幾つかのifやインクリメント) stream.WriteByte(byte >> 0); stream.WriteByte(byte >> 8) ... // 7. MemoryStreamのToArrayはbyte[]コピー memoryStream.ToArray(); } そりゃ遅い!!! し、ゴミも発生しまくる
  16. 16. // ZeroFormatterのばやい byte[] Serialize<T>(T obj) { // 1. Static変数からの最速での子シリアライザの取得 var formatter = Formatter<DefaultResolver, T>.Default; // 2. 当然ボクシングは全くなく、内部構造も全てジェネリクスで統一されてる formatter.Serialize(T value); // 3. 長さが既知の場合は長さを取って生成できる var bytes = new byte[formatter.GetLength()]; // 4. バイト配列に直接書く(WriteInt32の中身は *((int*)b) = value; だけ) BinaryUtil.WriteInt32(ref bytes, value); // 5. 出来たバイト配列をそのまま返すだけ return bytes; }
  17. 17. // ZeroFormatterのばやい byte[] Serialize<T>(T obj) { // 1. Static変数からの最速での子シリアライザの取得 var formatter = Formatter<DefaultResolver, T>.Default; // 2. 当然ボクシングは全くなく、内部構造も全てジェネリクスで統一されてる formatter.Serialize(T value); // 3. 長さが既知の場合は長さを取って生成できる var bytes = new byte[formatter.GetLength()]; // 4. バイト配列に直接書く(WriteInt32の中身は *((int*)b) = value; だけ) BinaryUtil.WriteInt32(ref bytes, value); // 5. 出来たバイト配列をそのまま返すだけ return bytes; } 汎用シリアライザながら 理論上最速とほぼ同等! ゴミも発生しない!
  18. 18. 1階層だけの抽象化 public abstract class Formatter<TTypeResolver, T> where TTypeResolver : ITypeResolver, new() { public abstract int? GetLength(); public abstract int Serialize(ref byte[] bytes, int offset, T value); public abstract T Deserialize(ref byte[] bytes, int offset, out int byteSize); }
  19. 19. internal class Int32ArrayFormatter : Formatter<Int32[]> { public override int Serialize(ref byte[] bytes, int offset, Int32[] value) { var writeSize = value.Length * 4; BinaryUtil.EnsureCapacity(ref bytes, offset, writeSize + 4); BinaryUtil.WriteInt32Unsafe(ref bytes, offset, value.Length); Buffer.BlockCopy(value, 0, bytes, offset + 4, writeSize); return writeSize + 4; } } // 例えばint[]の場合、普通はintの要素一個一個を処理するコードになる for (int i = 0; i < values.Length; i++) { stream.Write(serialize(values[i])); }
  20. 20. Conclusion
  21. 21. バイナリだから速いということはない シリアライザのパフォーマンスは実装が大事 よく出来たJSONシリアライザはその辺のバイナリ系より速い (実際 Jil はめちゃくちゃすごくよく出来た実装) 汎用シリアライザのチューニング可能箇所は無数 特にEnumは取扱注意で、語ると長くなりまうす それら無数に存在する全てを潰しきったのがZeroFormatter (パフォーマンス優先で潰し切ることが可能な設計になってる) というわけで、なので自信もってC#最速だといえます
  22. 22. C#における潮流の変化 競合相手の変化、GoなどのBetter C勢と戦わなければならない 富豪的プログラミングでOKで済ませられる局面は、特にフレーム ワークレイヤーでは厳しくなってきている(今まで以上に、ね) 言語(C# 7.0)とフレームワーク両面で性能に強い意識が働いている 抽象化をいかに薄くするかや、LINQを使わないということはLINQ をどう使うか、と同じぐらいの重要性を持った選択肢になってくる Corefxlab https://github.com/dotnet/corefxlab/ System.Slices - より効率的な配列やメモリ相互変換の取扱い System.IO.Pipelines - より高速なStream抽象 System.Text.Utf8 - UTF8変換へのオーバーヘッド低減

×