NFX-CLRでの超効率的なバイナリシリアル化

必要条件



この記事では、プロセスとマシンの間で複雑なオブジェクトを転送するタスクを検討します。 システムには、さまざまな構造の多数のビジネスオブジェクトを移動する必要がある多くの場所がありました。たとえば、







分散クラスターシステムで非常に重要な3つの側面について説明します。







上記の3つの側面について簡単に検討します。



最初は速度です。 これは、分散環境でシステム全体のパフォーマンスを確保するために非常に重要です。タスク(1人のユーザーからの要求など)を完了するには、他のバックエンドマシンに対して5〜10個の要求を実行する必要があります。



2番目はボリュームです。 大量のデータを転送/複製する場合、データセンター間の通信チャネルの予算を「肥大化」させないでください。



3番目は利便性です。 シリアル化/マーシャリングが「余分な」オブジェクト、ctrの作成のみを必要とする場合、それは非常に不便です。 データを転送します。 また、特定のビジネスタイプのプログラマーに低レベルのコードを記述させて、インスタンスをバイト配列に書き込むよう強制することも不便です。 5〜6クラスの場合にこれを行うことができますが、システムに30個の基本的な汎用クラス(つまりDeliveryStrategy)があり、それぞれが他の数十のクラスと組み合わされている場合(これにより、数百の特定のタイプ、つまりDeliveryStrategy、 DeliveryStrategy、DeliveryStrategyなど)。 追加のマークアップ、コードなどを必要とせずに、サブジェクト領域のほぼすべてのクラスをシリアル化できる透過的なシステムが欲しいです。 もちろん、管理されていないリソースやデリゲートなど、シリアル化する必要のないものもありますが、通常は他のすべて、構造体やクラスの読み取り専用フィールドなどの要素も必要です。



この記事では、バイナリシリアル化のトピックについて説明します。 上記の問題を効果的に解決することを目的としていないため、JSONおよびその他の形式については説明しません。



既存のシリアライザーの問題



すぐに予約します。ここに書かれているものはすべて、比較対象に応じて相対的です。 1秒間に数百のオブジェクトを読み書きする場合、問題はありません。 もう1つは、1秒間に数万または数十万ものオブジェクトを処理する必要がある場合です。



BinaryFormatterは.Netのベテランです。 使いやすく、DataContractSerializerよりも要件に適しています。 すべての組み込みコレクションタイプおよびその他のBCLクラスを十分にサポートします。 オブジェクトのバージョン管理をサポートします。 プラットフォーム間で相互運用できません。 非常に大きなパフォーマンス上の欠陥があります。 それは非常に遅く、シリアル化は非常に大規模なスレッドを生成します。



DataContractSerializer-WCFエンジン。 多くの場合、BinaryFormatterよりも高速に動作します。 相互運用性とバージョン管理をサポートします。 ただし、このシリアライザーは、汎用のシリアル化の問題自体を解決することを意図したものではありません。 クラスとフィールドを属性で特別に装飾する必要があります;ポリモーフィズムと複合型のサポートにも問題があります。 これは非常に説明がつきやすいです。 事実、DataContractSerializerは定義上、任意の型で動作することを意図したものではありません(名前の由来)。



プロトバフ-超高速! Google形式を使用して、オブジェクトのバージョンを変更し、超高速にすることができます。 プラットフォーム間で相互運用可能。 これには大きな重大な欠点があります-すべてのタイプを自動的に「理解」せず、複雑なグラフをサポートしません。



ThriftはFacebookの開発です。 IDLを使用し、言語間で相互運用可能で、バージョンを変更できます。 短所:動作が非常に遅く、大量のメモリを消費し、循環グラフをサポートしません。



上記の特性に基づいて、パフォーマンスを考慮しない場合、私たちにとって最も適切なシリアライザーはBinaryFormatterです。 彼は最も「透明」です。 プラットフォーム間の相互運用性をサポートしていないという事実は、私たちにとって重要ではありません。 1つのプラットフォーム-Unistackがあります。 しかし、彼の仕事のスピードはひどいです。 非常に遅くて大きな出力。



NFX.Serialization.Slim.SlimSerializer



github.com/aumcode/nfx/blob/master/Source/NFX/Serialization/Slim/SlimSerializer.cs



SlimSerializerは、特定のタイプごとに実行時に動的なser / deserコードを生成するハイブリッドシリアライザーです。



何かを犠牲にしなければならないので、私たちは絶対に普遍的な決定をしようとしませんでした。 私たちはそれをしませんでした。 つまり、私たちにとっては重要ではありません。





前述に基づいて、SlimSerializerは次のようなタスクには適していません。





SlimSerializerは、次のような状況向けに設計されています。





SlimSerializerは、次のようなすべての種類のエッジケースをサポートしています。





開発は簡単ではなく、すでに多くの最適化が行われています。 私たちが達成した結果は最終的なものではなく、加速することはできますが、これはすでに重要なコードの複雑さを引き起こします。



どのように機能しますか?



SlimSeralizerは、注入可能なフォーマットgithub.com/aumcode/nfx/blob/master/Source/NFX/IO/StreamerFormats.csからのストリーマーを使用します。 特定のタイプを直接ストリームにシリアル化するには、ストリーマー形式が必要です。 たとえば、デフォルトでは、FID、GUID、GDID、MetaHandleなどのタイプをサポートしています。 実際、特定のタイプは可変ビットエンコーディングによって巧妙にパッケージ化できます。 これにより、速度が大幅に向上し、スペースが節約されます。 すべての整数プリミティブは、可変ビットエンコーディングで記​​述されています。 したがって、特別な型の超高速サポートが必要な場合、StreamerFormatを継承し、WriteX / ReadXメソッドを追加できます。 システム自体がそれらを収集し、ラムダファンクタに変換します。ラムダファンクタは、高速シリアル化/逆シリアル化に必要です。



TypeDescriptor github.com/aumcode/nfx/blob/master/Source/NFX/Serialization/Slim/TypeSchema.csが構築され、シリアル化と逆シリアル化のために一対のファンクターを動的にコンパイルします。



SlimSerializerはTypeRegistryの考え方に基づいており、これはシリアライザー全体の主要なハイライトですgithub.com/aumcode/nfx/blob/master/Source/NFX/Serialization/Slim/TypeRegistry.cs 。 型は文字列として書き込まれます-型の完全な名前ですが、そのような型が既に検出されている場合は、「$ 123」という形式の型ハンドルが書き込まれます。 これは、レジストリ番号123にあるタイプを示します。



参照が見つかったら 、それをMetaHandle github.com/aumcode/nfx/blob/master/Source/NFX/IO/MetaHandle.csに置き換えます。これは、参照が文字列または整数である場合、文字列またはインスタンス番号である整数を効果的にインライン化しますオブジェクトグラフ内のオブジェクト、つまり 疑似ポインタハンドルの一種。 逆シリアル化では、すべてが逆の順序で再構築されます。



性能



以下のすべてのテストは、シングルスレッドのIntel Core I7 3.2 GHzで実行されました。

パフォーマンスSlimSerializerは、スレッドの数に比例してスケーリングします。 バッファーをコピーしないように、専用のスレッド静的最適化を使用します。



次のタイプを「実験的」としてください。 DataContractSerializerに必要なさまざまな属性に注意してください。



[DataContract(IsReference=true)] [Serializable] public class Perzon { [DataMember]public string FirstName; [DataMember]public string MiddleName; [DataMember]public string LastName; [DataMember]public Perzon Parent; [DataMember]public int Age1; [DataMember]public int Age2; [DataMember]public int? Age3; [DataMember]public int? Age4; [DataMember]public double Salary1; [DataMember]public double? Salary2; [DataMember]public Guid ID1; [DataMember]public Guid? ID2; [DataMember]public Guid? ID3; [DataMember]public List<string> Names1; [DataMember]public List<string> Names2; [DataMember]public int O1 = 1; [DataMember]public bool O2 = true; [DataMember]public DateTime O3 = App.LocalizedTime; [DataMember]public TimeSpan O4 = TimeSpan.FromHours(12); [DataMember]public decimal O5 = 123.23M; }
      
      







そして今では500,000個のオブジェクトを何回も実行しています:





スリムからBinFormatterへのシリアル化速度:13.37倍高速。

SlimからBinFormatterへの逆シリアル化速度:7.76倍高速。

スリムからBinFormatterボリューム:12.63倍少ない。



スリムからDataContractへのシリアル化速度:4.26倍高速。

SlimからDataContractへの逆シリアル化速度:7.89倍高速。

DataContractボリュームにスリム:8.22倍小さい。



そして今、配列やシートを含む数十の相互参照オブジェクトの複雑なオブジェクトグラフを試みています(多くの場合50,000を超えるオブジェクト):





SlimからBinFormatterへのシリアル化速度:5.85倍高速。

SlimからBinFormatterへのデシリアライズ速度:4.97倍高速。

スリムからBinFormatterボリューム:1.65倍小さい。



SlimからDataContractへのシリアル化速度:3.05倍高速。

SlimからDataContractへの逆シリアル化速度:7.49倍高速。

DataContractボリュームにスリム:4.53倍小さい。



型付きクラス(最初のケースは「Perzon」)と2番目(多くのオブジェクト)をシリアル化するときの違いに注意してください。 2番目のケースでは、オブジェクト間の周期的な関係を持つ複雑なグラフが存在するため、SlimはMicrosoftに近づき始めます(速度が低下します)。 ただし、速度の最後の最小値を4倍、ボリュームの半分倍も超えています。 このテストのコード: github.com/aumcode/nfx/blob/master/Source/Testing/Manual/WinFormsTest/SerializerForm2.cs#L51-104



そして、ここにApache.Thriftとの比較があります: blog.aumcode.com/2015/03/apache-thrift-vs-nfxglue-benchmark.html

これらの数値は純粋なシリアル化のためではなく、NFX.Glue全体(メッセージング、TCPネットワーク、セキュリティなどを含む)のためのものですが、速度は、ネイティブNFX.Glueバインダーが構築されるSlimSerializerに大きく依存します。



 Each test is: 64,000 calls each returning a set of 10 rows each having 10 fields 640,000 total rows pumped Glue: took 1982 msec @ 32290 calls/sec Thrift1: took 65299 msec @ 980 calls/sec 32x slower than Glue Thrift2: took 44925 msec @ 1424 calls/sec 22x slower than Glue ================================================================= Glue is: 32 times faster than Thrift BinaryProtocol 22 times faster than Thrift CompactProtocol
      
      







まとめ



NFX SlimSerializerは、非常に高い予測可能な堅牢なパフォーマンスを提供し、プロセッサとメモリのリソースを節約します。 これにより、CLRプラットフォームの高負荷テクノロジの機会が広がり、分散システムの各ノードで毎秒何十万ものリクエストを処理できるようになります。



SlimSerializerには、実用的な「すべてのサイズに対応する」システムを作成できないため、いくつかの制限があります。 これらの制限は、バージョン管理されたデータ構造の欠如、デリゲートのシリアル化、およびCLR以外のプラットフォームとの相互運用性です。 ただし、Unistackの概念(すべてのシステムノード用のソフトウェアの統合スタック)では、これらの制限は、バージョン管理の欠如を除いて一般に目に見えないことに注意する価値があります。 SlimSerializerは、データ構造が変更される可能性がある場合、ディスク上のデータの長期保存を目的とはしていません。



超効率的なNFX.Glueネイティブバインダーにより、シリアライザーで使用される特別な最適化により、プログラマーが余分なデータ転送タイプを処理する必要なく、毎秒10万+双方向の呼び出しに対応できます。



youtu.be/m5zckEbXAaA



youtu.be.com/KyhYwaxg2xc





SlimSerializerは、.NETに組み込まれているツールよりも大幅に優れており、相互に関連するオブジェクト(ProtobufもThriftも実行できない)の複雑なグラフを効率的に処理できます。



All Articles