2011-06-25 11 views
19

Mam strukturę posiadającą 3d współrzędnych w 3 ints. W teście zestawiłem listę <> z 1 miliona losowych punktów, a następnie wykorzystałem serialowanie binarne do strumienia pamięci.Popraw wydajność binarnej serializacji dla dużej listy struktur

strumień Pamięć zbliża się w ~ 21 MB - co wydaje się bardzo nieefektywne jako punkty 1000000 * 3 coords * 4 bajty powinny wychodzić na minimum 11MB

Jego biorąc również ~ 3 sekundy na moim stanowisku badawczym.

Wszelkie pomysły na poprawę wydajności i/lub rozmiaru?

(nie mam zachować interfejs ISerialzable czy to pomaga, mogę napisać bezpośrednio do strumienia pamięci)

EDIT - Z odpowiedzi poniżej Mam ułożyła showdown serializacji porównujące BinaryFormatter 'surowy' BinaryWriter i 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); 
     } 
    } 
} 

Wyniki (Uwaga PB v2.0.0.423 beta)

   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 

Oczywiście jest to po prostu spojrzenie na prędkość/rozmiar i nie bierze pod uwagę nic innego.

+1

@Ryan [ta odpowiedź] (http://stackoverflow.com/questions/703073/what- niedobory wbudowanego w binaryformatter-based-net-serializatio/703361 # 703361) sugerują użycie [protobuf-net] (http://code.google.com/p/protobuf-net) do szybkiej serializacji. – bzlm

+1

@Ryan i protobuf-net "v2" obsługuje structs. Pozwól mi spojrzeć później (nie na PC w tej chwili), ale jest to określona opcja. –

+1

Serializacja binarna używa refleksji. Jest to powolne, ale nigdy prawdziwe, ponieważ używasz go do operacji we/wy. Dlaczego serializacja do pamięci jest nie do zniesienia. –

Odpowiedz

10

Serializacja binarna przy użyciu BinaryFormatter zawiera informacje o typach w generowanych bajtach. Zajmuje to dodatkową przestrzeń. Jest to przydatne w przypadkach, gdy nie wiesz, na przykład, jakiej struktury danych oczekiwać po drugiej stronie.

W twoim przypadku wiesz jaki format mają dane na obu końcach i nie brzmi to tak, jakby się zmienił. Możesz więc napisać prostą metodę kodowania i dekodowania. Twoja klasa CoOrd nie musi już być możliwa do serializacji.

Chciałbym użyć System.IO.BinaryReader i System.IO.BinaryWriter, a następnie przepętlać każdą instancję CoOrd i odczytać/zapisać wartości proporcji X, Y, Z do strumienia. Te klasy nawet spakują twoje ints do mniej niż 11 MB, zakładając, że wiele twoich liczb jest mniejszych niż 0x7F i 0x7FFF.

coś takiego:

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

Aby odczytać ze strumienia:

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

"Serializacja binarna zawiera informacje o typach w bajtach, które generuje" - nie, "BinaryFormatter" to robi. Serializacja binarna * ogólnie * nie robi czegoś takiego. –

+2

Dobra, tak, właśnie o to chciałem się przekonać. Serializacja binarna _w ogóle jest pojęciem, a nie techniką. Zostanie edytowana w celu wyjaśnienia. –

3

Dla uproszczenia przy użyciu pre-build serializatora, polecam protobuf-net; tutaj jest v2 protobuf-net, ze po prostu dodając pewne atrybuty:

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

używając:

ProtoBuf.Serializer.Serialize(mStream, c); 

do serializacji. Zajmuje to 10 960 823 bajty, ale zauważ, że zmodyfikowałem SetupTestArray, aby ograniczyć rozmiar do 10.000, ponieważ domyślnie używa on "varint" kodującego na liczbach całkowitych, który zależy od rozmiaru. 10k nie ma tu znaczenia (w rzeczywistości nie sprawdzałem, jakie są "kroki").Jeśli wolisz stały rozmiar (co pozwoli dowolny zakres):

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

który odbywa 16,998,640 bajtów

+0

Wymieniasz atrybuty DataContract i DataMember - czy powinny one być ProtoContract i ProtoMember, czy też zostały źle zrozumiane? (ma PB v2.0.0.404) – Ryan

+0

@Ryan bez pomyłki; stara się być przyjazna. Wykorzysta Zamówienie z [DataMember] lub z [XmlElement], aby pomóc w przejściu z istniejących typów. W szczególności z LINQ do SQL. W v2 nie potrzebujesz nawet atrybutów (możesz osobno powiedzieć powiązania) –

+0

Masz także problem z deserializacją - Współrzędne c2 = ProtoBuf.Serializer.Deserialize (mStream) - pozostawia c2.Coords null. Włożyłem pełne źródło w edycji Q. Muszę przyznać, że nie poświęcono wystarczająco dużo czasu na RFM – Ryan

Powiązane problemy