【发布时间】:2017-08-29 18:56:05
【问题描述】:
我发现 ProtoBuf 在大量复杂对象序列化方面的性能非常奇怪。让我们有这两种情况:
A) 一个一个地序列化一个对象列表
B) 将列表作为一个整体序列化
凭直觉,这应该具有相似的性能。但是,在我的应用程序中,只需将对象放入列表并序列化列表,反序列化就有 10 倍的差异。 您可以在下面找到代码来测试它。在这个例子中,结果在 2 倍和 5 倍加速之间变化,但是在我的代码中,它的加速一致是 10 倍。
是什么原因造成的?我有一个应用程序,我需要一个一个序列化对象并且它的性能确实降级了,有什么方法可以提高一个一个序列化的性能 ?
谢谢
下面的代码输出
One by one serialization = 329204 ; deserialization = 41342
List serialization = 19531 ; deserialization = 27716
代码
[ProtoContract]
class TestObject
{
[ProtoMember(1)]public string str1;
[ProtoMember(2)]public string str2;
[ProtoMember(3)]public int i1;
[ProtoMember(4)]public int i2;
[ProtoMember(5)]public double d1;
[ProtoMember(6)]public double d2;
public TestObject(int cnt)
{
str1 = $"Hello World {cnt}";
str2 = $"Lorem ipsum {cnt}";
for (int i = 0; i < 2 ; i++) str1 = str1 + str1;
d1 = i1 = cnt;
d2 = i2 = cnt * 2;
}
public TestObject() { }
}
private void ProtoBufTest()
{
//init test data
List<TestObject> objects = new List<TestObject>();
int numObjects = 1000;
for(int i = 0; i < numObjects;i++)
{
objects.Add(new TestObject(i));
}
Stopwatch sw = new Stopwatch();
MemoryStream memStream = new MemoryStream();
//test 1
sw.Restart();
for (int i = 0; i < numObjects; i++)
{
ProtoBuf.Serializer.SerializeWithLengthPrefix<TestObject>(memStream, objects[i], ProtoBuf.PrefixStyle.Base128);
}
long timeToSerializeSeparately = sw.ElapsedTicks;
memStream.Position = 0;
sw.Restart();
for (int i = 0; i < numObjects; i++)
{
ProtoBuf.Serializer.DeserializeWithLengthPrefix<TestObject>(memStream, ProtoBuf.PrefixStyle.Base128);
}
long timeToDeserializeSeparately = sw.ElapsedTicks;
//test 2
memStream.Position = 0;
sw.Restart();
ProtoBuf.Serializer.SerializeWithLengthPrefix<List<TestObject>>(memStream, objects, ProtoBuf.PrefixStyle.Base128);
long timeToSerializeList = sw.ElapsedTicks;
memStream.Position = 0;
sw.Restart();
ProtoBuf.Serializer.DeserializeWithLengthPrefix<List<TestObject>>(memStream, ProtoBuf.PrefixStyle.Base128);
long timeToDeserializeList = sw.ElapsedTicks;
Console.WriteLine($"One by one serialization = {timeToSerializeSeparately} ; deserialization = {timeToDeserializeSeparately}");
Console.WriteLine($"List serialization = {timeToSerializeList} ; deserialization = {timeToDeserializeList}");
}
【问题讨论】:
-
@Sinatr 有没有办法强制 ProtoBuf 在调用之间缓存反射数据?这可能会有很大帮助
-
@Sinatr 实际上,这是对这段代码所显示内容的完全误解,而且您所说的几乎所有内容实际上都是错误的(100% 错误,实际上是倒置的)。当涉及到复杂的代码和性能时,没有“明显”。此测试中实际显示的是每种类型的反射成本和 JIT 成本,两者都只发生一次,并且该成本被错误地归因于单个对象序列化。 protobuf-net 对于单个根对象实际上比根列表更快。序列化根集合在这里是非常有害的。
-
@Sinatr 作为扩展:加速序列化根集合的一种厚颜无耻的方法是定义一个具有列表的单个根对象唯一的成员,从
Serialize(list)移动到Serialize(new ListWrapper { Items = list; });严重地。这允许元编程层在 IL 发射期间针对特定列表类型进行优化,而不是依赖于IEnumerable等。这也允许在定义迭代器时使用它们(就像它们用于List<T>等),并允许数组优化等。 -
@MarcGravell,我试图在不检查测试代码本身的情况下解释测试结果(这很糟糕,因为没有 警告 或重复测试)。指责反射是个坏主意,我可以想象如果有人会对我一开始优化的内容说坏话,对不起。显然(同样是这个词),这将是通过使用某种字典/缓存来优化的第一件事。
标签: c# .net list serialization protobuf-net