我最近的任务是探索在性能关键应用程序中使用protobuf-net。它目前使用Newtonsoft.Json,并且在大多数情况下,protobuf-net表现出了出色的性能提升。但在某些情况下,内存分配正在进行,我仍然不知道如何弄清楚发生了什么。
我已经整理了一个复制问题的小型控制台应用程序(问题最初是通过性能回归测试找到的)。由于显而易见的原因,我无法发布该确切的测试,但我有一个类似的例子;
public class Program
{
public static void Main(string[] args)
{
AppDomain.MonitoringIsEnabled = true;
var useProtoBuf = args.Length > 0;
if (useProtoBuf)
{
Console.WriteLine("Using protobuf-net");
}
else
{
Console.WriteLine("Using json.net");
}
var runtimeTypeModel = TypeModel.Create();
runtimeTypeModel.Add(typeof(TestResult), true);
var list = new List<Wrapper>();
for (var index = 0; index < 1_000_000; index++)
{
list.Add(new Wrapper
{
Value = "C5CAD058-3A05-48EA-9626-A6B4F692B14E"
});
}
var result = new TestResult
{
First = new CollectionWrapper
{
Collection = list
}
};
for (var i = 0; i < 25; i++)
{
if (useProtoBuf)
{
using (var stream = File.Create(@"..\..\protobuf-net.bin"))
{
runtimeTypeModel.Serialize(stream, result);
}
}
else
{
using (var stream = File.CreateText(@"..\..\json.net.json"))
using (var writer = new JsonTextWriter(stream))
{
new JsonSerializer().Serialize(writer, result);
}
}
}
Console.WriteLine($"Took: {AppDomain.CurrentDomain.MonitoringTotalProcessorTime.TotalMilliseconds:#,###} ms");
Console.WriteLine($"Allocated: {AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize / 1024:#,#} kb");
Console.WriteLine($"Peak Working Set: {Process.GetCurrentProcess().PeakWorkingSet64 / 1024:#,#} kb");
}
[ProtoContract]
public class Wrapper
{
[ProtoMember(1)]
public string Value { get; set; }
}
[ProtoContract]
public class TestResult
{
[ProtoMember(1)]
public CollectionWrapper First { get; set; }
}
[ProtoContract]
public class CollectionWrapper
{
[ProtoMember(1)]
public List<Wrapper> Collection { get; set; } = new List<Wrapper>();
}
}
我使用的是以下版本的软件包: -
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net47" />
<package id="protobuf-net" version="2.3.4" targetFramework="net47" />
</packages>
以下是我的结果: -
Foo.exe
Using json.net
Took: 12,000 ms
Allocated: 20,436 kb
Peak Working Set: 36,332 kb
Foo.exe 1
Using protobuf-net
Took: 5,203 ms
Allocated: 3,296,838 kb
Peak Working Set: 137,044 kb
任何帮助都将不胜感激。
非常感谢。
答案 0 :(得分:1)
是长度前缀强制缓冲的结果。这是在下一个“主要”版本中完全重做的东西(我有原型代码,它还没有准备好),以完全避免这个问题 - 使用一些狡猾的技巧来提前有效地计算所需的值。
在此期间,有一种可用的方法来阻止这种缓冲:使用“群组”。基本上,有两种方法可以在protobuf中编码子对象 - 长度前缀(默认值)或开始/结束哨兵。与JASON相比,您可以将这些标记视为{
和}
,但在protobuf中。要切换到此项,请将DataFormat = DataFormat.Group
添加到所有子对象[ProtoMember(...)]
属性,包括集合成员。这应该从根本上削减工作集,但是:它是一个不同的数据布局。大多数protobuf库可以很好地与团队合作,如果x-plat是一个问题,但要明确:谷歌已经决定团体===糟糕(这是一个耻辱,我爱他们!),他们不再存在proto3架构语法 - 但它们在proto2中。
在技术层面:
目前,请尝试:
[ProtoContract]
public class Wrapper
{
[ProtoMember(1)]
public string Value { get; set; }
}
[ProtoContract]
public class TestResult
{
[ProtoMember(1, DataFormat = DataFormat.Group)]
public CollectionWrapper First { get; set; }
}
[ProtoContract]
public class CollectionWrapper
{
[ProtoMember(1, DataFormat = DataFormat.Group)]
public List<Wrapper> Collection { get; set; } = new List<Wrapper>();
}