2011-06-25 1 views
19

私は3つの整数で3次元座標を保持する構造体を持っています。テストでは、100万のランダムポイントのリスト<をまとめて、メモリストリームにバイナリシリアル化を使用しました。構造体の大きなリストのバイナリの直列化パフォーマンスを改善します

メモリストリームは〜21メガバイトに来ている - 1000000ポイントとして非常に非効率です* 3つのCOORDS * 4バイト

そのまた、私のテストリグ上〜3秒を取って11メガバイトの最小で出てくるはずです。

性能やサイズを改善するためのアイデアはありますか?

(私はそれが助け場合、私はメモリストリームに直接書き出すことができISerialzableインターフェイスを維持する必要はありません)

EDIT - に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を取る

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

を、[この回答](http://stackoverflow.com/questions/703073/what- [protobuf-net](http://code.google.com/p/protobuf-net)を使用して、バイナリフォーマッタベースのネットシリアル/ 703361#703361の欠陥を示唆しています。高速シリアル化が可能です。 – bzlm

+1

@ Ryanとprotobuf-net "v2"は構造体をサポートしています。私は後で(現時点ではPCではなく)見てみましょうが、それは確実な選択肢です。 –

+1

バイナリシリアル化ではReflectionを使用します。これは遅いですが、I/Oに使用するため実際の問題ではありません。なぜあなたはメモリにシリアライズするのかはわかりません。 –

答えて

10

BinaryFormatterを使用するバイナリシリアル化には、生成するバイトの型情報が含まれています。これは追加のスペースを占有します。例えば、相手側でどのようなデータ構造が必要なのかわからない場合に便利です。

あなたのケースでは、データの両端にどのようなフォーマットがあり、それが変わるようには聞こえません。したがって、単純なエンコードとデコードの方法を書くことができます。 CoOrdクラスはもはやシリアライズ可能である必要はありません。

私はSystem.IO.BinaryReaderとSystem.IO.BinaryWriterを使用して、各CoOrdインスタンスをループし、X、Y、Zプロパティ値をストリームに読み書きします。これらのクラスは、あなたの数字の多くが0x7Fと0x7FFFより小さいと仮定して、あなたのintを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

そうです、それは私が乗ろうとしていたポイントです。 2進直列化_一般に_は概念であり、技術ではありません。明確にするために編集します。 –

3

を、私はprotobuf-netをお勧めします。

[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); 

をシリアル化するために、ここではいくつかの属性を追加しているProtobufネットv2は、あります。これは10,960,823バイトを要しますが、デフォルトではサイズに依存する整数に対して "varint"エンコーディングを使用するので、サイズを10,000に制限するためにSetupTestArrayを調整しました。 10kはここでは重要ではありません(実際には、「ステップ」が何であるかは確認していません)。あなたが(任意の範囲をできるようになります)固定サイズ好む場合:16998640バイト

+0

属性DataContractとDataMemberをリストアップしようとしています - それらがProtoContractとProtoMemberであるか、または私が誤解しているか? (PB v2.0.0.404) – Ryan

+0

@ライアン間違いなし。それは収容しようとします。既存の型からの移行を支援するために、[DataMember]または[XmlElement]のOrderを使用します。特に、LINQ-to-SQLから。 v2では、属性も必要ありません(バインディングを個別に伝えることができます) –

+0

デシリアライズに問題があります。 - 座標c2 = ProtoBuf.Serializer.Deserialize (mStream) - c2.Coordsをnullにします。私はQの編集で完全なソースを入れました。 RFMに十分な時間が与えられていないことを認めなければならない – Ryan

関連する問題