Protobuf.net 内存使用情况

Posted

技术标签:

【中文标题】Protobuf.net 内存使用情况【英文标题】:Protobuf.net Memory Usage 【发布时间】:2011-12-09 18:43:59 【问题描述】:

嘿嘿。 protobuf.net 的长期爱好者。

不过是快速提问。我有一个高度多线程的 C# 应用程序,它可能每秒反序列化 100 个对象,大约为 50MB/秒。我看到非常大的内存使用量,远远超过我反序列化的内存使用量。我已经通过“Red Gate ANTS Memory Profiler”运行了该应用程序,并且由于 protobuf(超过 50% 的应用程序使用率),它向我显示了大量的第 2 代内存对象。大多数对象都是 int 值并与:

- TypeModel.TryDeserializeList()
- ProtoBuf.Meta.BasicList

如能帮助减少此第 2 代内存使用量,我们将不胜感激。

马克

【问题讨论】:

很有趣——你能大致说明一下这个模型的样子吗?它是一个整数数组吗?一个整数列表?另外:这是什么框架?完整的.NET? CF? SL?可能可以很容易地解决这个问题,但需要更多的上下文来确定。 另外 - root 对象是列表吗?基本上...可以危险编码吗? (或者至少,类似的东西) 好的。为缺乏细节而道歉。您所说的“模型是什么样的?”是什么意思。我要反序列化的最大对象是使用 .net 4.0 的 int[33554432] 数组。 不幸的是,没有我可以提供的特定代码片段,我知道这是导致问题的原因。内存分析器似乎给我的所有信息是,在 Gen 2 和大型对象堆中都有大量的 in 值以某种方式与 ProtoBuf.Meta.BasicList 相关联。还值得补充的是,当所有反序列化完成并且我调用手动垃圾收集时,应用程序的内存使用量下降到反序列化时的 20%。这只是反序列化如此大的 int 数组时 protobuf 的预期内存使用情况吗? 我想知道的主要是...Deserialize<T> 中使用的 T 是什么 - 是 Deserialize<int[]> 吗? 【参考方案1】:

在我看来,这里的根 T 是数组本身,即

int[] values = Serializer.Deserialize<int[]>(source);

如果是这种情况,那么 当前 它会为该场景使用稍微次优的路径(原因是:即使在具有弱元编程/反射模型的平台,例如 ios)。我会尝试在某个时候花几个小时来整理它,但为了回答你的问题 - 你应该能够通过添加父对象来避免这里的问题:

[ProtoContract]
public class MyDataWrapper  // need a new name...
    [ProtoMember(1)]
    public int[] Values  get;set; 

然后:

int[] values = Serializer.Deserialize<MyDataWrapper>(source).Values;

这实际上与已经通过Serialize&lt;int[]&gt; 序列化的数据完全兼容,只要使用的字段编号是1。这种方法的另一个好处是,如果需要,您可以使用“打包”子格式(仅适用于原语列表/数组,例如 int);尽管在这种情况下这可能仍然不是一个好主意,因为长度很大(序列化时可能需要缓冲)。


附加上下文;这里的“v1”基本上使用 MakeGenericType 即时切换到上面的内容;然而,由于这种方法在“v2”目标的许多其他平台中不可用,因此这里使用了一种不太优雅的方法。但现在它已经相当稳定了,我可以在完整的 .NET 2.0 或更高版本上运行时重新添加优化版本。

【讨论】:

是的,这就是问题所在。添加包装器并没有其他任何东西可以完全消除大量内存使用。再次感谢马克。 @MarcF k;我会看看我是否可以重新添加优化,以备将来使用 @MarcGravell 我知道这是一个老问题,但我遇到了同样的 GC 问题。您能否详细说明一下解决方法(使用包装类):这是否适用于所有数组类型(即 ComplexType[] 作为根对象,其中 ComplexType 可以使用继承建模)?包装和数组解决方案是否完全等效(即 protobuf 在序列化数组时是否仅生成包装类)?最后,您是否考虑过在 v. 2.1.0(预发布版)中解决此问题? 是的,然后它将使用不同的代码路径(尽管数据将相同);不,它没有经过大修;我不记得这是否会对 GC 产生重大影响,但值得一试。对于最外层的类型:不,它不会简单地将其包装在内部 @MarcGravell 感谢您的快速回复。我在下面的答案中添加了一些基准测试结果。效果非常明显,因此对适当平台的代码进行特殊处理可能是有意义的。【参考方案2】:

为了详细说明 Marcs 的答案,我做了一个快速基准测试

使用包装器与使用数组的序列化/反序列化。 启用/不启用服务器 GC

基准测试创建了 100.000 个复杂对象(1 个时间跨度、2 个双精度数、2 个整数、2 个整数?s、具有 0 到 4 个短元素(1 个字符)的字符串列表)并重复序列化/反序列化过程 30 次并测量总时间和运行期间发生的 GC 收集次数。结果是(在 VS 之外的 Release 中运行)

GC IsServer: False, GC latency: Interactive, GC LOH compaction: Default
Wrapper serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 20.363 s
------------------------
Array serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 30.433 s
------------------------
Wrapper deserialization
Generation 0: 109 collects
Generation 1: 47 collects
Generation 2: 16 collects
Time: 71.277 s
------------------------
Array deserialization
Generation 0: 129 collects
Generation 1: 57 collects
Generation 2: 19 collects
Time: 89.145 s


GC IsServer: True, GC latency: Interactive, GC LOH compaction: Default
Wrapper serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 20.430 s
------------------------
Array serialization
Generation 0: 0 collects
Generation 1: 0 collects
Generation 2: 0 collects
Time: 30.364 s
------------------------
Wrapper deserialization
Generation 0: 4 collects
Generation 1: 3 collects
Generation 2: 2 collects
Time: 39.452 s
------------------------
Array deserialization
Generation 0: 3 collects
Generation 1: 3 collects
Generation 2: 3 collects
Time: 47.546 s

所以我的结论是

包装器方法有利于序列化和反序列化(后者具有更明显的效果)。 在没有服务器 GC 的情况下运行时,阵列方法带来的 GC 收集开销更加明显。另请注意,不运行服务器 GC 并在多个线程上反序列化时,GC 性能影响非常糟糕(不包括结果)。

希望有人觉得这很有用。

(不幸的是,基准代码取决于内部代码,因此我无法在此处发布完整代码。

【讨论】:

以上是关于Protobuf.net 内存使用情况的主要内容,如果未能解决你的问题,请参考以下文章

ProtoBuf.Net - 使用 Proto 作为 TypeFormatter

linux怎么查看内存大小,linux怎么查看内存使用情况

使用 protobuf.net 序列化图形时出现问题

使用 DataContract 属性时 ProtoBuf.NET 未序列化

Linux如何查看内存使用状况

如何使用Linux命令行查看Linux服务器内存使用情况