Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
Work
C#
Unity
Private
http://neue.cc/
@neuecc
https://github.com/neuecc/UniRx
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
using
ZeroFormatter
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
秘訣は...
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
評価を遅らせる
無限大高速なシリアライザ
https://github.com/neuecc/ZeroFormatter/
シリアライズも速い
無限大高速なシリアライザ
https://github.com/neuecc/ZeroFormatter/
シリアライズも速い
Benchmark
本当に速いの?
はい。
ZeroFormatterに見るC#で最速のシリアライザを作成する100億の方法
辻ベンチマーク
Serialization Process
例えばint(999)をbyte[]にシリアライズ
var bytes = BitConverter.GetBytes(999);
unsafe
{
var bytes = new byte[4];
fixed (byte* ptr = bytes)
{
*((int*)ptr) = 999;
}
}
// ふつーのシリアライザの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();
}
// ふつーのシリアライザの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();
}
そりゃ遅い!!!
し、ゴミも発生しまくる
// 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;
}
// 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;
}
汎用シリアライザながら
理論上最速とほぼ同等!
ゴミも発生しない!
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);
}
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]));
}
Conclusion
バイナリだから速いということはない
シリアライザのパフォーマンスは実装が大事
よく出来たJSONシリアライザはその辺のバイナリ系より速い
(実際 Jil はめちゃくちゃすごくよく出来た実装)
汎用シリアライザのチューニング可能箇所は無数
特にEnumは取扱注意で、語ると長くなりまうす
それら無数に存在する全てを潰しきったのがZeroFormatter
(パフォーマンス優先で潰し切ることが可能な設計になってる)
というわけで、なので自信もってC#最速だといえます
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変換へのオーバーヘッド低減

More Related Content

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