2012-07-03 11 views
8

私たちのアプリケーションでは、とりわけチャンクされたバイトのリスト(現在はList<byte[]>として公開されています)が含まれています。バイト配列を大きなオブジェクトのヒープに置くことができれば、時間がたつにつれメモリの断片化に悩まされるので、バイトをチャンクアップします。Protobuf-netを使用したチャンク付きバイト配列のメモリ使用のシリアライズ

また、Protobuf-netを使用して、独自の生成されたシリアル化DLLを使用してこれらの構造をシリアル化しています。

しかし、私たちは、Protobuf-netがシリアル化中に非常に大きなインメモリバッファを作成していることに気付きました。ソースコードを見てみると、List<byte[]>構造体全体が書き込まれるまで内部バッファをフラッシュすることができないかもしれないと思われます。なぜならバッファの後ろに合計長さを書き込む必要があるからです。

これは残念なことに、最初にバイトをチャンクすることで私たちの仕事を元に戻し、メモリの断片化のためにOutOfMemoryExceptionsを与えます(Protobuf-netがバッファを84k以上に拡張しようとしているときに例外が発生します。それをLOHに置き、プロセス全体のメモリ使用量はかなり低い)。

Protobuf-netがどのように機能しているかを分析したところ、この問題を回避する方法はありますか?


更新

マルクの答えに基づいて、ここで私が試したものです:

[ProtoContract] 
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)] 
public class ABase 
{ 
} 

[ProtoContract] 
public class A : ABase 
{ 
    [ProtoMember(1, DataFormat = DataFormat.Group)] 
    public B B 
    { 
     get; 
     set; 
    } 
} 

[ProtoContract] 
public class B 
{ 
    [ProtoMember(1, DataFormat = DataFormat.Group)] 
    public List<byte[]> Data 
    { 
     get; 
     set; 
    } 
} 

は、その後、それをシリアル化するために:しかし

var a = new A(); 
var b = new B(); 
a.B = b; 
b.Data = new List<byte[]> 
{ 
    Enumerable.Range(0, 1999).Select(v => (byte)v).ToArray(), 
    Enumerable.Range(2000, 3999).Select(v => (byte)v).ToArray(), 
}; 

var stream = new MemoryStream(); 
Serializer.Serialize(stream, a); 

を私が固執する場合ブレークポイントはProtoWriter.WriteBytes()となります。DemandSpace()メソッドの最後に向かってDemandSpace()に入ると、writer.flushLock1に等しいので、バッファがフラッシュされていないことがわかります。

私はこのようなABASEのための別の基本クラスを作成する場合:

[ProtoContract] 
[ProtoInclude(1, typeof(ABase), DataFormat = DataFormat.Group)] 
public class ABaseBase 
{ 
} 

[ProtoContract] 
[ProtoInclude(1, typeof(A), DataFormat = DataFormat.Group)] 
public class ABase : ABaseBase 
{ 
} 

その後writer.flushLockDemandSpace()2に等しいです。

派生型とここでは分かりませんでした。

答えて

5

私はここにいくつかの行間を読むつもりです... List<T>は(いるProtobuf用語でrepeatedとしてマッピング)全体の長さプレフィックスを持っている、とbyte[]ていません(bytesとしてマッピング)些細な長さのプレフィックスを持っているので、追加のバッファリングを引き起こすべきではありません。だから私はあなた実際を持っているが、よりどのようなものであるかを推測している:に基本的には、A.Fooを書くときにここで

[ProtoContract] 
public class A { 
    [ProtoMember(1)] 
    public B Foo {get;set;} 
} 
[ProtoContract] 
public class B { 
    [ProtoMember(1)] 
    public List<byte[]> Bar {get;set;} 
} 

、長さプレフィックスのためにバッファリングする必要が実際にあるを宣言する「以下の複雑なデータがありますA.Fooの値」を参照)。あなたがの長さを示すマーカーを得る意味、

  • デフォルト(Googleの明記好み)は長さ接頭辞です:

    [ProtoMember(1, DataFormat=DataFormat.Group)] 
    public B Foo {get;set;} 
    

    これはいるProtobufで2つの梱包技術の間で変更します。幸い、簡単な修正はありますメッセージは、サブメッセージ・ペイロード、

  • に従うことなく、スタートマーカー、サブメッセージ・ペイロード、及びエンドマーカを使用するオプションもある

2番目のテクニックを使用する場合、バッファーする必要はありません、そうです:そうではありません。これは、同じデータに対してわずかに異なるバイトを書き込むことを意味しますが、protobuf-netは非常に寛容です。のデータを喜んでの形式にデシリアライズします。意味:この変更を行っても、既存のデータは読み込めますが、新しいデータでは開始/終了マーカー技法が使用されます。

これは、なぜGoogleが長さ接頭辞アプローチを好むのですか? おそらくを読むと、length-prefixアプローチを使用しているときにフィールドをスキップする(生のリーダーAPIまたは不要な/予想外のデータとして)場合は、ストリーム[n]バイトだけ進めてください。これとは対照的に、開始/終了マーカーを使用してデータをスキップするには、サブフィールドを個別にスキップしてペイロードをクロールする必要があります。もちろん、この理論的な読取りパフォーマンスの違いは、がそのデータをと期待している場合には当てはまりません。また、Googleのprotobufの実装では、通常のPOCOモデルでは動作しないため、ペイロードのサイズは既に分かっているため、実際に書くときに同じ問題は発生しません。

+0

クイック返信をありがとう。私たちのデータ構造についてのあなたの推測は正しいものでした。 Aへの参照を含むプロパティについても、DataFormatをGroupに変更し、オブジェクトグラフのルートまで同様に変更する必要があると言うのは間違いありませんか?また、この変更は関連するProtoInclude属性にも必要ですか? –

+0

@Jamesは本質的にはいです。うーん...おそらく、私はモデルレベルのデフォルトを追加するべきです! –

+0

DataFormat.Groupを使用して問題を解決しようとしたところで私の質問を更新しましたが、まだバッファをフラッシュするのに問題があります。私がばかだと謝罪.. –

2

さらに編集してください。 [ProtoInclude(..., DataFormat=...)]は処理されていないようです。私は私の現在のローカルビルドでは、このためのテストを追加した、それが今渡し:

[Test] 
public void Execute() 
{ 

    var a = new A(); 
    var b = new B(); 
    a.B = b; 

    b.Data = new List<byte[]> 
    { 
     Enumerable.Range(0, 1999).Select(v => (byte)v).ToArray(), 
     Enumerable.Range(2000, 3999).Select(v => (byte)v).ToArray(), 
    }; 

    var stream = new MemoryStream(); 
    var model = TypeModel.Create(); 
    model.AutoCompile = false; 
#if DEBUG // this is only available in debug builds; if set, an exception is 
    // thrown if the stream tries to buffer 
    model.ForwardsOnly = true; 
#endif 
    CheckClone(model, a); 
    model.CompileInPlace(); 
    CheckClone(model, a); 
    CheckClone(model.Compile(), a); 
} 
void CheckClone(TypeModel model, A original) 
{ 
    int sum = original.B.Data.Sum(x => x.Sum(b => (int)b)); 
    var clone = (A)model.DeepClone(original); 
    Assert.IsInstanceOfType(typeof(A), clone); 
    Assert.IsInstanceOfType(typeof(B), clone.B); 
    Assert.AreEqual(sum, clone.B.Data.Sum(x => x.Sum(b => (int)b))); 
} 

これはコミットいくつかの他の、無関係なリファクタリング(WinRTの/ IKVMのためのいくつかのリワーク)に接続されていますが、できるだけ早くコミットする必要があります。

関連する問題