9

私たちのアプリケーションには、特にバイトのチャンクリストを含むいくつかのデータ構造があります(現在は として公開されていますList<byte[]>)。バイト配列を大きなオブジェクト ヒープに配置できるようにすると、時間の経過とともにメモリの断片化が発生するため、バイトをチャンクアップします。

また、独自に生成したシリアライゼーション DLL を使用して、Protobuf-net を使用してこれらの構造をシリアライズすることも開始しました。

ただし、シリアル化中に 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()て呼び出す場所にブレークポイントを設定してにステップインすると、 equals が原因でバッファーがフラッシュされていないことがわかります。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.flushLock等しい。2DemandSpace()

派生型を扱うために、ここで見逃した明らかなステップがあると思いますか?

4

2 に答える 2

6

ここでいくつかの行間を読みます... List<T>( repeatedprotobuf 用語のようにマッピングされた) には全体的な長さのプレフィックスがなく、byte[](としてマッピングされたbytes) には追加のバッファリングを引き起こさない単純な長さのプレフィックスがあるためです。だから私はあなたが実際に持っているものはもっと似ていると思います:

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

ここで、 length-prefix をバッファリングする必要があるのは、実際には を書き込むときA.Fooであり、基本的には「次の複素数データが​​ の値である」と宣言するA.Fooためです)。幸いなことに、簡単な修正方法があります。

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

これは、protobuf の 2 つのパッキング手法の間で変わります。

  • デフォルト (Google の設定) は長さのプレフィックスです。つまり、メッセージの長さを示すマーカーを取得し、次にサブメッセージ ペイロードを取得します。
  • ただし、開始マーカー、サブメッセージ ペイロード、および終了マーカーを使用するオプションもあります。

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

これは疑問を投げかけます: なぜグーグルは長さプレフィックスのアプローチを好むのですか? おそらくこれは、長さプレフィックスのアプローチを使用する場合、フィールドをスキップして (未加工のリーダー API を介して、または不要/予期しないデータとして)読み取りを行う方が効率的であるためです。ストリームを [n] バイト進めます。対照的に、開始/終了マーカーでデータをスキップするには、サブフィールドを個別にスキップしてペイロードをクロールする必要があります。もちろん、この読み取りパフォーマンスの理論上の違いは、期待している場合には当てはまりません。そのデータをオブジェクトに読み込もうとしますが、これはほぼ確実に行います。また、Google の protobuf 実装では、通常の POCO モデルでは機能しないため、ペイロードのサイズは既にわかっているため、書き込み時に同じ問題が実際に発生することはありません。

于 2012-07-04T06:31:05.693 に答える
3

あなたの編集についての追加; 単に処理されていないように[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 の一部のリワーク) に関連付けられていますが、できるだけ早くコミットする必要があります。

于 2012-07-08T22:30:48.493 に答える