2011-06-25 31 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