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)));
}