.net BinaryFormatter序列化对象慢的原因
最近在做组件对象写入流的优化,因此对一些.net下序列化组件做了一些测试,分别针对ProtoBuf.net 和.net自带的BinaryFormatter进行了分析.从测试的结果来看BinaryFormatter的性能和ProtoBuf.net的性能足足相差了10倍。为什么差这么远呢,如果紧紧从运行时间来看可能以为BinaryFormatter一定是使用反射什么的,所以导致结果这么慢。为了更清楚的了解具体情况于是对两者的测试代码进行了一个内存分析.
ProtoBuf.net的测试代码
public void PB(int count) { TestPB obj = new TestPB(); obj.Email = "henryfan@test.com"; obj.FirstName = "henry"; obj.LastName = "fan"; obj.ID = 3456; obj.Phone = "13418888121"; obj.Type = 67; System.IO.MemoryStream stream = new System.IO.MemoryStream(265); stream.Position = 0; ProtoBuf.Serializer.Serialize<TestPB>(stream, obj); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Reset(); sw.Start(); for (int i = 0; i < count; i++) { stream.Position = 0; ProtoBuf.Serializer.Serialize<TestPB>(stream, obj); } sw.Stop(); Console.WriteLine("ProtoBuf Serialize Objects{0} Time:{1}", count, sw.Elapsed.TotalMilliseconds); }
BinaryFormatter的测试代码
public void DotNet(int count) { TestDotNet obj = new TestDotNet(); obj.Email = "henryfan@test.com"; obj.FirstName = "henry"; obj.LastName = "fan"; obj.ID = 3456; obj.Phone = "13418888121"; obj.Type = 67; System.IO.MemoryStream stream = new System.IO.MemoryStream(256); BinaryFormatter bf = new BinaryFormatter(); stream.Position = 0; bf.Serialize(stream, obj); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Reset(); sw.Start(); for (int i = 0; i < count; i++) { stream.Position = 0; bf.Serialize(stream, obj); } sw.Stop(); Console.WriteLine("BinaryFormatter Serialize Objects{0} Time:{1}", count,sw.Elapsed.TotalMilliseconds); }
通过内存分析针对上面代码进行100000次的序列化对象查看其内存开销的情况
ProtoBuf.net 的内存使用情况
名称 | 非独占分配数 | 独占分配数 | 非独占字节数 | 独占字节数 | 非独占分配数百分比 | |
+ | ProtoBuf.ProtoWriter | 100,001 | 100,001 | 6,000,060 | 6,000,060 | 24.44 |
+ | ProtoBuf.NetObjectCache | 100,001 | 100,001 | 2,400,024 | 2,400,024 | 24.44 |
+ | ProtoBuf.Meta.RuntimeTypeModel.TypeFinder | 200,002 | 200,002 | 2,400,024 | 2,400,024 | 48.88 |
+ | System.Byte[] | 1,199 | 1,199 | 2,111,060 | 2,111,060 | 0.29 |
+ | System.String | 697 | 697 | 32,418 | 32,418 | 0.17 |
+ | System.Char[] | 44 | 44 | 25,382 | 25,382 | 0.01 |
+ | System.Object[] | 350 | 350 | 20,300 | 20,300 | 0.09 |
+ | System.Collections.Generic.List`1 | 589 | 589 | 14,136 | 14,136 | 0.14 |
+ | System.Reflection.Emit.OpCode | 226 | 226 | 9,944 | 9,944 | 0.06 |
+ | System.Reflection.MethodInfo[] | 256 | 256 | 9,012 | 9,012 | 0.06 |
+ | System.Reflection.RuntimeMethodInfo | 123 | 123 | 6,888 | 6,888 | 0.03 |
+ | System.Collections.ArrayList | 259 | 259 | 6,216 | 6,216 | 0.06 |
BinaryFormatter的内存使用情况
名称 | 非独占分配数 | 独占分配数 | 非独占字节数 | 独占字节数 | 非独占分配数百分比 | |
+ | System.Collections.Hashtable.bucket[] | 500,008 | 500,008 | 72,001,656 | 72,001,656 | 8.77 |
+ | System.Object[] | 600,012 | 600,012 | 39,204,636 | 39,204,636 | 10.52 |
+ | System.Byte[] | 201,074 | 201,074 | 31,699,205 | 31,699,205 | 3.53 |
+ | System.Collections.Hashtable | 500,008 | 500,008 | 28,000,448 | 28,000,448 | 8.77 |
+ | System.Runtime.Serialization.Formatters.Binary.NameInfo | 400,004 | 400,004 | 19,200,192 | 19,200,192 | 7.01 |
+ | System.String | 100,078 | 100,078 | 19,005,036 | 19,005,036 | 1.75 |
+ | System.Int64[] | 100,001 | 100,001 | 17,200,172 | 17,200,172 | 1.75 |
+ | System.Runtime.Serialization.Formatters.Binary.ObjectWriter | 100,001 | 100,001 | 10,400,104 | 10,400,104 | 1.75 |
+ | System.Runtime.Serialization.Formatters.Binary.__BinaryWriter | 100,001 | 100,001 | 10,000,100 | 10,000,100 | 1.75 |
+ | System.Runtime.Serialization.Formatters.Binary.WriteObjectInfo | 100,001 | 100,001 | 6,800,068 | 6,800,068 | 1.75 |
+ | System.Runtime.Serialization.Formatters.Binary.SerStack | 200,002 | 200,002 | 4,800,048 | 4,800,048 | 3.51 |
+ | System.Runtime.Serialization.Formatters.Binary.BinaryObjectWithMapTyped | 100,001 | 100,001 | 4,400,044 | 4,400,044 | 1.75 |
从上面两个内存分析结果来看就一目了然了,ProtoBuf.net在序列化的过程中紧紧只开销了13MB左右的内存,所创建对象的总数大概在400000个左右;反观BinaryFormatter在序列化过程确使用了300多MB的内存,创建对象总数接近6000000个。紧紧是对象的创建对象的数量就已经是ProtoBuf.net 10倍,因此效率慢就是正常的事。MS为什么这样做这个就不得而知了,明明可以做得很好的,但确并没这样做……
访问Beetlex的Github