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

27

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

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

Work

C#

Unity

Private

http://neue.cc/

@neuecc

https://github.com/neuecc/UniRx

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

using

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

ZeroFormatter

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

秘訣は...

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

評価を遅らせる

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

無限大高速なシリアライザ

https://github.com/neuecc/ZeroFormatter/

シリアライズも速い

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

無限大高速なシリアライザ

https://github.com/neuecc/ZeroFormatter/

シリアライズも速い

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

Benchmark

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

本当に速いの?

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

はい。

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

辻ベンチマーク

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

Serialization Process

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

例えばint(999)をbyte[]にシリアライズ

var bytes = BitConverter.GetBytes(999);

unsafe{

var bytes = new byte[4];fixed (byte* ptr = bytes){

*((int*)ptr) = 999;}

}

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

// ふつーのシリアライザのAPIの例byte[] Serialize<T>(T obj){

// 1. 内部での書き込みストリーム作りのためにnew MemoryStreamusing(var stream = new MemoryStream())

// 2. データ生成時の内部ステートを保持するためのWriterのnewvar writer = new XxxWriter(stream);

// 3. Int用子シリアライザの取得あるいはprimitiveの場合はswitchvar 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();

}

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

// ふつーのシリアライザのAPIの例byte[] Serialize<T>(T obj){

// 1. 内部での書き込みストリーム作りのためにnew MemoryStreamusing(var stream = new MemoryStream())

// 2. データ生成時の内部ステートを保持するためのWriterのnewvar writer = new XxxWriter(stream);

// 3. Int用子シリアライザの取得あるいはprimitiveの場合はswitchvar 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();

}

そりゃ遅い!!!し、ゴミも発生しまくる

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

// 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;

}

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

// 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;

}

汎用シリアライザながら理論上最速とほぼ同等!ゴミも発生しない!

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

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);

}

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

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]));}

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

Conclusion

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

バイナリだから速いということはないシリアライザのパフォーマンスは実装が大事

よく出来たJSONシリアライザはその辺のバイナリ系より速い

(実際 Jilはめちゃくちゃすごくよく出来た実装)

汎用シリアライザのチューニング可能箇所は無数特にEnumは取扱注意で、語ると長くなりまうす

それら無数に存在する全てを潰しきったのがZeroFormatter

(パフォーマンス優先で潰し切ることが可能な設計になってる)

というわけで、なので自信もってC#最速だといえます

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

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変換へのオーバーヘッド低減