【问题标题】:protobuf-net NOT faster than binary serialization?protobuf-net 不比二进制序列化快吗?
【发布时间】:2010-06-03 13:42:33
【问题描述】:

我编写了一个程序来使用 XMLSerializer、BinaryFormatter 和 ProtoBuf 序列化一个“Person”类。我认为 protobuf-net 应该比其他两个更快。 Protobuf 序列化比 XMLSerialization 快,但比二进制序列化慢得多。我的理解不正确吗?请让我明白这一点。谢谢你的帮助。

编辑:- 我更改了代码(在下面更新)以测量仅用于序列化而不是创建流的时间,并且仍然看到差异。谁能告诉我为什么?

以下是输出:-

在 347 毫秒内使用协议缓冲区创建人员

使用 XML 在 1462 毫秒内创建人

在 2 毫秒内使用二进制创建人

代码如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProtoBuf;
using System.IO;
using System.Diagnostics;
using System.Runtime.Serialization.Formatters.Binary;
namespace ProtocolBuffers
{
    class Program
    {
        static void Main(string[] args)
        {

            string folderPath  = @"E:\Ashish\Research\VS Solutions\ProtocolBuffers\ProtocolBuffer1\bin\Debug";
            string XMLSerializedFileName = Path.Combine(folderPath,"PersonXMLSerialized.xml");
            string ProtocolBufferFileName = Path.Combine(folderPath,"PersonProtocalBuffer.bin");
            string BinarySerializedFileName = Path.Combine(folderPath,"PersonBinary.bin");

            if (File.Exists(XMLSerializedFileName))
            {
                File.Delete(XMLSerializedFileName);
                Console.WriteLine(XMLSerializedFileName + " deleted");
            }
            if (File.Exists(ProtocolBufferFileName))
            {
                File.Delete(ProtocolBufferFileName);
                Console.WriteLine(ProtocolBufferFileName + " deleted");
            }
            if (File.Exists(BinarySerializedFileName))
            {
                File.Delete(BinarySerializedFileName);
                Console.WriteLine(BinarySerializedFileName + " deleted");
            }

            var person = new Person
            {
                Id = 12345,
                Name = "Fred",
                Address = new Address
                {
                    Line1 = "Flat 1",
                    Line2 = "The Meadows"
                }
            };

            Stopwatch watch = Stopwatch.StartNew();

            using (var file = File.Create(ProtocolBufferFileName))
            {
                watch.Start();
                Serializer.Serialize(file, person);
                watch.Stop();
            }

            //Console.WriteLine(watch.ElapsedMilliseconds.ToString());
            Console.WriteLine("Person got created using protocol buffer in " + watch.ElapsedMilliseconds.ToString() + " milliseconds ");

            watch.Reset();

            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(person.GetType());
            using (TextWriter w = new StreamWriter(XMLSerializedFileName))
            {
                watch.Start();
                x.Serialize(w, person);
                watch.Stop();
            }

            //Console.WriteLine(watch.ElapsedMilliseconds.ToString());
            Console.WriteLine("Person got created using XML in " + watch.ElapsedMilliseconds.ToString() + " milliseconds");

            watch.Reset();

            using (Stream stream = File.Open(BinarySerializedFileName, FileMode.Create))
            {
                BinaryFormatter bformatter = new BinaryFormatter();
                //Console.WriteLine("Writing Employee Information");
                watch.Start();
                bformatter.Serialize(stream, person);
                watch.Stop();
            }

            //Console.WriteLine(watch.ElapsedMilliseconds.ToString());
            Console.WriteLine("Person got created using binary in " + watch.ElapsedMilliseconds.ToString() + " milliseconds");

            Console.ReadLine();



        }
    }


    [ProtoContract]
    [Serializable]
    public class Person
    {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Name { get; set; }
        [ProtoMember(3)]
        public Address Address { get; set; }
    }
    [ProtoContract]
    [Serializable]
    public class Address
    {
        [ProtoMember(1)]
        public string Line1 { get; set; }
        [ProtoMember(2)]
        public string Line2 { get; set; }
    }
}

【问题讨论】:

  • 一些快速注意事项 - 首先,尽量减少外部因素对您的测试的影响。序列化到内存流或其他一些相对性能中立的目标,而不是文件系统。其次,您应该只为序列化操作计时 - 不要包括创建流或构建对象。第三,重复测试合理次数并报告汇总结果。
  • 感谢 cmets。您提到了“相对性能中立的目标而不是文件系统”。这意味着什么?您能否举一些“相对绩效目标”的例子?谢谢。
  • @Ashish - 我主要考虑的是内存流。如果您序列化到内存流,环境可能仍然会影响您的测试(例如,内存压力可能会迫使您进入虚拟内存进行一项测试而不是另一项测试),但我认为它会与文件系统相比,不太可能影响您的结果。回想起来,重复测试可能比尝试获得绝对中立的测试条件更重要,但努力争取这些条件不会受到伤害。 ;)
  • @Jeff Sternal - 或者至少可以在 using 语句中移动秒表。那么文件的创建或文件的关闭就没有延迟了。
  • @Jeff Sternal - 感谢您抽出宝贵时间。实时,我肯定需要创建相当多的文件,我不能将它们保存到内存流中。您认为汇总(创建大量文件)时间比较会有所不同吗?

标签: serialization protobuf-net


【解决方案1】:

我已回复您的电子邮件;我没有意识到你也在这里发布了它。我的第一个问题是:哪个版本的 protobuf-net?我问的原因是“v2”的 development 主干故意禁用了自动编译,这样我就可以使用我的单元测试来测试运行时和预编译版本。因此,如果您使用“v2”(仅在源代码中可用),您需要告诉它编译模型 - 否则它正在运行 100% 反射。

在“v1”或“v2”中,您可以这样做:

Serializer.PrepareSerializer<Person>();

完成此操作后,我得到的数字(来自您电子邮件中的代码;我没有检查上面是否是同一个示例):

10
Person got created using protocol buffer in 10 milliseconds
197
Person got created using XML in 197 milliseconds
3
Person got created using binary in 3 milliseconds

另一个因素是重复;坦率地说,3-10ms 不算什么;你无法比较这个级别的数字。将其提高到重复 5000 次(重复使用 XmlSerializer / BinaryFormatter 实例;没有引入虚假成本)我得到:

110
Person got created using protocol buffer in 110 milliseconds
329
Person got created using XML in 329 milliseconds
133
Person got created using binary in 133 milliseconds

把它带到更愚蠢的极端(100000):

1544
Person got created using protocol buffer in 1544 milliseconds
3009
Person got created using XML in 3009 milliseconds
3087
Person got created using binary in 3087 milliseconds

所以最终:

  • 当您几乎没有要序列化的数据时,大多数方法都会非常快(包括 protobuf-net)
  • 随着您添加数据,差异变得更加明显; protobuf 通常在这里表现出色,无论是对于单个大图,还是对于许多小图

另请注意,在“v2”中,编译后的模型可以完全静态编译(到您可以部署的 dll),甚至可以消除(已经很小的)启动成本。

【讨论】:

  • 马克,绝对! “protobuf-serialized”文件的大小较小,当您测试大量文件时,时间明显少于二进制文件。感谢您的时间。 :-)
  • 认为你们也可以用反序列化时间来补充这一点?这也很重要。
  • @David 我不在电脑前,但“非常快”非常接近 :) 如果你 非常 想要 这个例子 我可以做到,但我有无数其他现有的措施,都说“快”,在数字方面 - 任何一个吗?当然,您自己的数据会更有说服力
【解决方案2】:

我的意见与标记的答案略有不同。我认为这些测试的数字反映了二进制格式化程序的元数据开销。 BinaryFormatter 在写入数据之前先写入关于类的元数据,而 protobuf 只写入数据。

对于测试中非常小的对象(一个 Person 对象),二进制格式化程序的元数据成本比实际情况更重要,因为它写入的元数据多于数据。所以,当你增加重复次数时,元数据成本被夸大了,在极端情况下会达到与 xml 序列化相同的水平。

如果您序列化一个 Person 数组,并且该数组足够大,那么元数据成本对总成本将是微不足道的。然后二进制格式化程序应该执行类似于 protobuf 的极端重复测试。

PS:我发现这个页面是因为我正在评估不同的序列化程序。我还发现了一个博客http://blogs.msdn.com/b/youssefm/archive/2009/07/10/comparing-the-performance-of-net-serializers.aspx,它显示了 DataContractSerializer + 二进制 XmlDictionaryWriter 的测试结果比二进制格式化程序好几倍。它还用非常小的数据进行了测试。当我自己用大数据进行测试时,我惊讶地发现结果非常不同。因此,请使用您将实际使用的真实数据进行测试。

【讨论】:

    【解决方案3】:

    我们不断地序列化相当大的对象(大约 50 个属性),所以我写了一个小测试来比较 BinaryFormatter 和 protobuf-net,就像你做的那样,这是我的结果(10000 个对象):

    BinaryFormatter serialize: 316
    BinaryFormatter deserialize: 279
    protobuf serialize: 243
    protobuf deserialize: 139
    BinaryFormatter serialize: 315
    BinaryFormatter deserialize: 281
    protobuf serialize: 127
    protobuf deserialize: 110
    

    这显然是一个非常明显的区别。第二次运行(测试完全相同)也比第一次快得多。

    更新。执行 RuntimeTypeModel.Add..Compile 生成以下结果:

    BinaryFormatter serialize: 303
    BinaryFormatter deserialize: 282
    protobuf serialize: 113
    protobuf deserialize: 50
    BinaryFormatter serialize: 317
    BinaryFormatter deserialize: 266
    protobuf serialize: 126
    protobuf deserialize: 49
    

    【讨论】:

      【解决方案4】:

      如果我们在内存中进行比较,硬编码序列化在某些情况下会更快。 如果你的类很简单,也许会更好地编写你自己的序列化器......

      稍作修改的代码:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using ProtoBuf;
      using System.IO;
      using System.Diagnostics;
      using System.Runtime.Serialization.Formatters.Binary;
      
      namespace ProtocolBuffers
      {
          class Program
          {
              static void Main(string[] args)
              {
      
                  string folderPath = @"../Debug";
                  string XMLSerializedFileName = Path.Combine(folderPath, "PersonXMLSerialized.xml");
                  string ProtocolBufferFileName = Path.Combine(folderPath, "PersonProtocalBuffer.bin");
                  string BinarySerializedFileName = Path.Combine(folderPath, "PersonBinary.bin");
                  string BinarySerialized2FileName = Path.Combine(folderPath, "PersonBinary2.bin");
      
                  if (File.Exists(XMLSerializedFileName))
                  {
                      File.Delete(XMLSerializedFileName);
                      Console.WriteLine(XMLSerializedFileName + " deleted");
                  }
                  if (File.Exists(ProtocolBufferFileName))
                  {
                      File.Delete(ProtocolBufferFileName);
                      Console.WriteLine(ProtocolBufferFileName + " deleted");
                  }
                  if (File.Exists(BinarySerializedFileName))
                  {
                      File.Delete(BinarySerializedFileName);
                      Console.WriteLine(BinarySerializedFileName + " deleted");
                  }
                  if (File.Exists(BinarySerialized2FileName))
                  {
                      File.Delete(BinarySerialized2FileName);
                      Console.WriteLine(BinarySerialized2FileName + " deleted");
                  }
      
                  var person = new Person
                  {
                      Id = 12345,
                      Name = "Fred",
                      Address = new Address
                      {
                          Line1 = "Flat 1",
                          Line2 = "The Meadows"
                      }
                  };
      
                  Stopwatch watch = Stopwatch.StartNew();
      
                  using (var file = new MemoryStream())
                  //using (var file = File.Create(ProtocolBufferFileName))
                  {
                      watch.Start();
                      for (int i = 0; i < 100000; i++)
                          Serializer.Serialize(file, person);
                      watch.Stop();
                  }
      
                  Console.WriteLine("Person got created using protocol buffer in " + watch.ElapsedMilliseconds.ToString() + " milliseconds ");
      
                  watch.Reset();
      
                  System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(person.GetType());
                  using (var w = new MemoryStream())
                  //using (TextWriter w = new StreamWriter(XMLSerializedFileName))
                  {
                      watch.Start();
                      for (int i = 0; i < 100000; i++)
                          x.Serialize(w, person);
                      watch.Stop();
                  }
      
                  Console.WriteLine("Person got created using XML in " + watch.ElapsedMilliseconds.ToString() + " milliseconds");
      
                  watch.Reset();
      
                  using (var stream = new MemoryStream())
                  //using (Stream stream = File.Open(BinarySerializedFileName, FileMode.Create))
                  {
                      BinaryFormatter bformatter = new BinaryFormatter();
                      watch.Start();
                      for (int i = 0; i < 100000; i++)
                          bformatter.Serialize(stream, person);
                      watch.Stop();
                  }
      
                  Console.WriteLine("Person got created using binary in " + watch.ElapsedMilliseconds.ToString() + " milliseconds");
      
                  watch.Reset();
      
                  using (var stream = new MemoryStream())
                  //using (Stream stream = File.Open(BinarySerialized2FileName, FileMode.Create))
                  {
                      BinaryWriter writer = new BinaryWriter(stream);
                      watch.Start();
                      for (int i = 0; i < 100000; i++)
                          writer.Write(person.GetBytes());
                      watch.Stop();
                  }
      
                  Console.WriteLine("Person got created using binary2 in " + watch.ElapsedMilliseconds.ToString() + " milliseconds");
      
                  Console.ReadLine();
              }
          }
      
      
          [ProtoContract]
          [Serializable]
          public class Person
          {
              [ProtoMember(1)]
              public int Id { get; set; }
              [ProtoMember(2)]
              public string Name { get; set; }
              [ProtoMember(3)]
              public Address Address { get; set; }
      
              public byte[] GetBytes()
              {
                  using (var stream = new MemoryStream())
                  {
                      BinaryWriter writer = new BinaryWriter(stream);
      
                      writer.Write(this.Id);
                      writer.Write(this.Name);
                      writer.Write(this.Address.GetBytes());
      
                      return stream.ToArray();
                  }
              }
      
              public Person()
              {
              }
      
              public Person(byte[] bytes)
              {
                  using (var stream = new MemoryStream(bytes))
                  {
                      BinaryReader reader = new BinaryReader(stream);
      
                      Id = reader.ReadInt32();
                      Name = reader.ReadString();
      
                      int bytesForAddressLenght = (int)(stream.Length - stream.Position);
                      byte[] bytesForAddress = new byte[bytesForAddressLenght];
                      Array.Copy(bytes, (int)stream.Position, bytesForAddress, 0, bytesForAddressLenght);
                      Address = new Address(bytesForAddress);
                  }
              }
          }
          [ProtoContract]
          [Serializable]
          public class Address
          {
              [ProtoMember(1)]
              public string Line1 { get; set; }
              [ProtoMember(2)]
              public string Line2 { get; set; }
      
              public byte[] GetBytes()
              {
                  using(var stream = new MemoryStream())
                  {
                      BinaryWriter writer = new BinaryWriter(stream);
      
                      writer.Write(this.Line1);
                      writer.Write(this.Line2);
      
                      return stream.ToArray();
                  }
              }
      
              public Address()
              {
      
              }
      
              public Address(byte[] bytes)
              {
                  using(var stream = new MemoryStream(bytes))
                  {
                      BinaryReader reader = new BinaryReader(stream);
      
                      Line1 = reader.ReadString();
                      Line2 = reader.ReadString();
                  }
              }
          }
      }
      

      和我的结果:

      Person got created using protocol buffer in 141 milliseconds
      Person got created using XML in 676 milliseconds
      Person got created using binary in 525 milliseconds
      Person got created using binary2 in 79 milliseconds
      

      【讨论】:

        猜你喜欢
        • 2011-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多