2011-06-25 93 views
19

我有一个结构,以3个整数存储3d坐标。在一个测试中,我将百万个随机点列表放在一起,然后将二进制序列化用于内存流。提高大型结构列表的二进制序列化性能

内存流是在1〜21 MB未来 - 这似乎1000000点非常低效的* 3个* COORDS 4个字节应该在11MB最低出来

它也正在〜3秒对我的试验台。

提高性能和/或大小的任何想法?

(我没有保持ISerialzable接口,如果有帮助,我可以直接写出到内存流)

编辑 - 从下面的答案我已经把一个序列化摊牌比较的BinaryFormatter , '原始' 的BinaryWriter和的Protobuf

using System; 
using System.Text; 
using System.Collections.Generic; 
using System.Linq; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 
using System.IO; 
using ProtoBuf; 

namespace asp_heatmap.test 
{ 
    [Serializable()] // For .NET BinaryFormatter 
    [ProtoContract] // For Protobuf 
    public class Coordinates : ISerializable 
    { 
     [Serializable()] 
     [ProtoContract] 
     public struct CoOrd 
     { 
      public CoOrd(int x, int y, int z) 
      { 
       this.x = x; 
       this.y = y; 
       this.z = z; 
      } 
      [ProtoMember(1)]    
      public int x; 
      [ProtoMember(2)] 
      public int y; 
      [ProtoMember(3)] 
      public int z; 
     } 

     internal Coordinates() 
     { 
     } 

     [ProtoMember(1)] 
     public List<CoOrd> Coords = new List<CoOrd>(); 

     public void SetupTestArray() 
     { 
      Random r = new Random(); 
      List<CoOrd> coordinates = new List<CoOrd>(); 
      for (int i = 0; i < 1000000; i++) 
      { 
       Coords.Add(new CoOrd(r.Next(), r.Next(), r.Next())); 
      } 
     } 

     #region Using Framework Binary Formatter Serialization 

     void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) 
     { 
      info.AddValue("Coords", this.Coords); 
     } 

     internal Coordinates(SerializationInfo info, StreamingContext context) 
     { 
      this.Coords = (List<CoOrd>)info.GetValue("Coords", typeof(List<CoOrd>)); 
     } 

     #endregion 

     # region 'Raw' Binary Writer serialization 

     public MemoryStream RawSerializeToStream() 
     { 
      MemoryStream stream = new MemoryStream(Coords.Count * 3 * 4 + 4); 
      BinaryWriter writer = new BinaryWriter(stream); 
      writer.Write(Coords.Count); 
      foreach (CoOrd point in Coords) 
      { 
       writer.Write(point.x); 
       writer.Write(point.y); 
       writer.Write(point.z); 
      } 
      return stream; 
     } 

     public Coordinates(MemoryStream stream) 
     { 
      using (BinaryReader reader = new BinaryReader(stream)) 
      { 
       int count = reader.ReadInt32(); 
       Coords = new List<CoOrd>(count); 
       for (int i = 0; i < count; i++)     
       { 
        Coords.Add(new CoOrd(reader.ReadInt32(),reader.ReadInt32(),reader.ReadInt32())); 
       } 
      }   
     } 
     #endregion 
    } 

    [TestClass] 
    public class SerializationTest 
    { 
     [TestMethod] 
     public void TestBinaryFormatter() 
     { 
      Coordinates c = new Coordinates(); 
      c.SetupTestArray(); 

      // Serialize to memory stream 
      MemoryStream mStream = new MemoryStream(); 
      BinaryFormatter bformatter = new BinaryFormatter(); 
      bformatter.Serialize(mStream, c); 
      Console.WriteLine("Length : {0}", mStream.Length); 

      // Now Deserialize 
      mStream.Position = 0; 
      Coordinates c2 = (Coordinates)bformatter.Deserialize(mStream); 
      Console.Write(c2.Coords.Count); 

      mStream.Close(); 
     } 

     [TestMethod] 
     public void TestBinaryWriter() 
     { 
      Coordinates c = new Coordinates(); 
      c.SetupTestArray(); 

      MemoryStream mStream = c.RawSerializeToStream(); 
      Console.WriteLine("Length : {0}", mStream.Length); 

      // Now Deserialize 
      mStream.Position = 0; 
      Coordinates c2 = new Coordinates(mStream); 
      Console.Write(c2.Coords.Count); 
     } 

     [TestMethod] 
     public void TestProtoBufV2() 
     { 
      Coordinates c = new Coordinates(); 
      c.SetupTestArray(); 

      MemoryStream mStream = new MemoryStream(); 
      ProtoBuf.Serializer.Serialize(mStream,c); 
      Console.WriteLine("Length : {0}", mStream.Length); 

      mStream.Position = 0; 
      Coordinates c2 = ProtoBuf.Serializer.Deserialize<Coordinates>(mStream); 
      Console.Write(c2.Coords.Count); 
     } 
    } 
} 

结果(注PB v2.0.0.423测试版)

   Serialize | Ser + Deserialize | Size 
-----------------------------------------------------------   
BinaryFormatter 2.89s |  26.00s !!!  | 21.0 MB 
ProtoBuf v2  0.52s |  0.83s   | 18.7 MB 
Raw BinaryWriter 0.27s |  0.36s   | 11.4 MB 

显然,这只是看速度/大小,并没有考虑到其他任何东西。

+1

@Ryan,[这个答案](http://stackoverflow.com/questions/703073/what- (内置二进制格式化基础的网络序列化/ 703361#703361)建议使用[protobuf-net](http://code.google.com/p/protobuf-net)用于快速序列化。 – bzlm

+1

@Ryan和protobuf-net“v2”支持结构。让我稍后再看看(目前不在PC上),但这是一个明确的选择。 –

+1

二进制序列化使用反射。这很慢,但从来不是一个真正的问题,因为您将它用于I/O。为什么你序列化到内存是不可想象的。 –

回答

10

使用BinaryFormatter的二进制序列化包括它生成的字节中的类型信息。这占用了额外的空间。例如,在您不知道在另一端需要的数据结构的情况下,这很有用。

就你而言,你知道数据在两端有什么格式,而且听起来不会改变。所以你可以写一个简单的编码和解码方法。您的CoOrd类不再需要可序列化。

我会使用System.IO.BinaryReader和System.IO.BinaryWriter,然后遍历每个CoOrd实例并读取/写入X,Y,Z propery值到流中。假设你的许多数字都小于0x7F和0x7FFF,这些类甚至可以将你的整数打包到11MB以内。

像这样:

using (var writer = new BinaryWriter(stream)) { 
    // write the number of items so we know how many to read out 
    writer.Write(points.Count); 
    // write three ints per point 
    foreach (var point in points) { 
     writer.Write(point.X); 
     writer.Write(point.Y); 
     writer.Write(point.Z); 
    } 
} 

为了从流读出:

List<CoOrd> points; 
using (var reader = new BinaryReader(stream)) { 
    var count = reader.ReadInt32(); 
    points = new List<CoOrd>(count); 
    for (int i = 0; i < count; i++) { 
     var x = reader.ReadInt32(); 
     var y = reader.ReadInt32(); 
     var z = reader.ReadInt32(); 
     points.Add(new CoOrd(x, y, z)); 
    } 
} 
+2

“二进制序列化包括它生成的字节中的类型信息” - 不,“BinaryFormatter”可以做到这一点。二进制序列化*一般*不会做这样的事情。 –

+2

没错,是的,这正是我想要解决的问题。二进制序列化_一般_是一个概念,而不是技术。将编辑澄清。 –

3

对于使用预生成串行的简单起见,我建议protobuf-net;这里是protobuf网V2,只需加入一些属性:

[DataContract] 
public class Coordinates 
{ 
    [DataContract] 
    public struct CoOrd 
    { 
     public CoOrd(int x, int y, int z) 
     { 
      this.x = x; 
      this.y = y; 
      this.z = z; 
     } 
     [DataMember(Order = 1)] 
     int x; 
     [DataMember(Order = 2)] 
     int y; 
     [DataMember(Order = 3)] 
     int z; 
    } 
    [DataMember(Order = 1)] 
    public List<CoOrd> Coords = new List<CoOrd>(); 

    public void SetupTestArray() 
    { 
     Random r = new Random(123456); 
     List<CoOrd> coordinates = new List<CoOrd>(); 
     for (int i = 0; i < 1000000; i++) 
     { 
      Coords.Add(new CoOrd(r.Next(10000), r.Next(10000), r.Next(10000))); 
     } 
    } 
} 

使用:

ProtoBuf.Serializer.Serialize(mStream, c); 

序列化。这需要10,960,823字节,但请注意,我调整了SetupTestArray将大小限制为10,000,因为默认情况下它对整数使用“varint”编码,这取决于大小。 10k在这里并不重要(实际上我没有检查“步骤”是什么)。如果你喜欢一个固定的大小(这将使任何范围):

 [ProtoMember(1, DataFormat = DataFormat.FixedSize)] 
     int x; 
     [ProtoMember(2, DataFormat = DataFormat.FixedSize)] 
     int y; 
     [ProtoMember(3, DataFormat = DataFormat.FixedSize)] 
     int z; 

这需要16998640字节

+0

您正在列出属性DataContract和DataMember - 他们应该是ProtoContract和ProtoMember还是我误解了? (有PB v2.0.0.404) – Ryan

+0

@瑞安没有错;它试图容纳。它将使用[DataMember]或[XmlElement]中的Order来帮助从现有类型转换。特别是从LINQ到SQL。在v2中,你甚至不需要属性(你可以单独告诉它的绑定) –

+0

还有麻烦的反序列化 - 坐标c2 = ProtoBuf.Serializer.Deserialize (mStream) - 离开c2.Coords为空。我已经在Q编辑中提供了完整的源代码。不得不承认没有给足够的时间来RFM – Ryan