Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/340.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 用Protobuf-net序列化分块字节数组的内存使用_C#_Protobuf Net_Chunking_Large Object Heap - Fatal编程技术网

C# 用Protobuf-net序列化分块字节数组的内存使用

C# 用Protobuf-net序列化分块字节数组的内存使用,c#,protobuf-net,chunking,large-object-heap,C#,Protobuf Net,Chunking,Large Object Heap,在我们的应用程序中,我们有一些数据结构,其中包括一个字节的分块列表(当前公开为列表)。我们将字节分块,因为如果我们允许将字节数组放在大型对象堆上,那么随着时间的推移,我们会遭受内存碎片的困扰 我们还开始使用Protobuf net来序列化这些结构,使用我们自己生成的序列化DLL 然而,我们注意到Protobuf net在序列化时创建了非常大的内存缓冲区。浏览一下源代码,它似乎无法刷新其内部缓冲区,直到整个列表结构被写入,因为它需要在之后写入缓冲区前端的总长度 不幸的是,这首先破坏了我们对字节进行

在我们的应用程序中,我们有一些数据结构,其中包括一个字节的分块列表(当前公开为
列表
)。我们将字节分块,因为如果我们允许将字节数组放在大型对象堆上,那么随着时间的推移,我们会遭受内存碎片的困扰

我们还开始使用Protobuf net来序列化这些结构,使用我们自己生成的序列化DLL

然而,我们注意到Protobuf net在序列化时创建了非常大的内存缓冲区。浏览一下源代码,它似乎无法刷新其内部缓冲区,直到整个
列表
结构被写入,因为它需要在之后写入缓冲区前端的总长度

不幸的是,这首先破坏了我们对字节进行分块的工作,并最终导致内存碎片导致的OutOfMemoryException(异常发生在Protobuf net试图将缓冲区扩展到84k以上时,这显然使其处于LOH状态,并且我们的整个进程内存使用率相当低)

如果我对Protobuf网络工作原理的分析是正确的,有没有办法解决这个问题


更新

根据Marc的回答,以下是我尝试过的:

[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;
    }
}
然后
writer.flushLock
等于
DemandSpace()中的
2


我猜,对于派生类型,这里有一个明显的步骤我错过了?

我将在这里读几行之间的内容。。。因为
List
(用protobuf的说法映射为
repeated
)没有总长度前缀,
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;}
}
protobuf中两种包装技术之间的变化:

  • 默认设置(google声明的首选项)是带有长度前缀的,这意味着您将获得一个标记,指示要遵循的消息的长度,然后是子消息负载
  • 但也可以选择使用开始标记、子消息有效负载和结束标记
当使用第二种技术时,它不需要缓冲,所以:它不需要。这确实意味着它将为相同的数据写入稍有不同的字节,但protobuf net非常宽容,并且将很高兴地从任何一种格式反序列化数据。含义:如果进行了此更改,您仍然可以读取现有数据,但新数据将使用开始/结束标记技术


这就需要一个问题:为什么谷歌更喜欢长度前缀方法?这可能是因为在使用长度前缀方法时,在读取字段时跳过字段(通过原始读取器API或作为不需要的/意外的数据)更有效,因为您可以只读取长度前缀,然后只对流[n]字节进行处理;相反,要使用开始/结束标记跳过数据,您仍然需要在有效负载中爬行,逐个跳过子字段。当然,如果您期望数据并希望将其读入您的对象,那么这种读取性能的理论差异并不适用,您几乎肯定会这样做。此外,在google protobuf实现中,由于它不适用于常规的POCO模型,有效负载的大小已经知道,因此它们在编写时不会真正看到相同的问题。

请重新编辑;
[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)));
}
[测试]
public void Execute()
{
var a=新的a();
var b=新的b();
a、 B=B;
b、 数据=新列表
{
Enumerable.Range(01999)。选择(v=>(字节)v.ToArray(),
Enumerable.Range(20003999)。选择(v=>(字节)v.ToArray(),
};
var stream=newmemoryStream();
var model=TypeModel.Create();
model.AutoCompile=false;
#如果DEBUG//,则仅在调试版本中可用;如果设置,则会出现异常
//如果流试图缓冲,则引发
model.ForwardsOnly=true;
#恩迪夫
CheckClone(型号a);
model.CompileInPlace();
CheckClone(型号a);
选中克隆(model.Compile(),a);
}
void CheckClone(类型模型,原件)
{
intsum=original.B.Data.sum(x=>x.sum(B=>(int)B));
var clone=(A)model.DeepClone(原始);
Assert.IsInstanceOfType(typeof(A),clone);
Assert.IsInstanceOfType(typeof(B),clone.B);
arenequal(sum,clone.B.Data.sum(x=>x.sum(B=>(int)B));
}

此提交与其他一些无关的重构(WinRT/IKVM的一些返工)相关联,但应尽快提交。

感谢您的快速回复。你对我们数据结构的猜测是正确的。我说的是否正确,我们需要将包含对a的引用的任何属性的DataFormat更改为Group,等等,直到对象图的根?而且这种改变也需要在相关的ProtoInclude属性上进行吗?@James本质上是的。六羟甲基三聚氰胺六甲醚。。。也许我应该为此添加一个模型级默认值!我已经更新了我的问题,尝试使用DataFormat.Group来解决这个问题,但在刷新缓冲区方面仍然存在问题。如果我是个白痴,我道歉。。
[ProtoContract]
public class A {
    [ProtoMember(1)]
    public B Foo {get;set;}
}
[ProtoContract]
public class B {
    [ProtoMember(1)]
    public List<byte[]> Bar {get;set;}
}
[ProtoMember(1, DataFormat=DataFormat.Group)]
public B Foo {get;set;}
[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)));
}