2010-06-03 4 views
8

Ich schrieb ein Programm, um eine 'Person'-Klasse mit XMLSerializer, BinaryFormatter und ProtoBuf zu serialisieren. Ich dachte protobuf-net sollte schneller sein als die anderen beiden. Die Protobuf-Serialisierung war schneller als die XML-Serialisierung, aber viel langsamer als die Binärserialisierung. Ist mein Verständnis falsch? Bitte lass mich das verstehen. Danke für die Hilfe.protobuf-net NICHT schneller als binäre Serialisierung?

EDIT: - Ich änderte den Code (aktualisiert unten), um die Zeit nur für die Serialisierung zu messen und nicht die Streams zu erstellen und immer noch den Unterschied zu sehen. Kann mir einer sagen warum?

Im Anschluss an die Ausgabe: -

Person wurde unter Verwendung von Protokollpuffer in 347 Millisekunden erstellt

Person wurde unter Verwendung von XML in 1462 Millisekunden erstellt

Person wurde unter Verwendung von binären in 2 Millisekunden erstellt

Code unter

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; } 
    } 
} 
+2

Ein paar schnelle Notizen - zuerst versuchen, Reduzieren Sie den Einfluss externer Faktoren auf Ihren Test. Serialisierung in einen Speicherstream oder ein anderes relativ leistungsneutrales Ziel anstelle des Dateisystems. Zweitens sollten Sie nur die Serialisierungszeit angeben - schließen Sie nicht die Erstellung Ihrer Streams oder die Konstruktion von Objekten ein. Drittens, wiederholen Sie Ihre Tests eine angemessene Anzahl von Malen und melden Sie die aggregierten Ergebnisse. –

+0

Danke für die Kommentare. Sie haben "relativ leistungsneutrales Ziel und nicht das Dateisystem" erwähnt. Was bedeutet das? Könnten Sie bitte einige Beispiele für ein "relativ leistungsneutrales Ziel" nennen? Vielen Dank. –

+1

@Ashish - Ich dachte in erster Linie an einen Gedächtnisstrom. Die Umgebung * könnte sich immer noch auf Ihre Tests auswirken, wenn Sie in einen Speicherstream serialisieren (zum Beispiel könnte der Speicherdruck Sie zwingen, für einen Test in den virtuellen Speicher zu gehen und nicht in den anderen), aber ich denke, es wäre weniger wahrscheinlich, Ihren zu beeinflussen Ergebnisse als das Dateisystem. Im Nachhinein ** ist es wahrscheinlich wichtiger, Ihre Tests zu wiederholen, als absolut neutrale Testbedingungen ** zu bekommen, aber das Streben nach diesen Bedingungen wird nicht schaden. ;) –

Antwort

23

Ich antwortete auf Ihre E-Mail; Ich wusste nicht, dass du es auch hier gepostet hast. Die erste Frage, die ich habe, ist: Welche Version von Protobuf-Net? Der Grund, warum ich frage ist, dass die Entwicklung Stamm von "v2" absichtlich Auto-Kompilierung deaktiviert hat, so dass ich meine Komponententests verwenden kann, um sowohl die Laufzeit als auch die vorkompilierten Versionen zu testen. Wenn Sie also "v2" verwenden (nur in der Quelle verfügbar), müssen Sie ihm mitteilen, dass das Modell kompiliert werden soll - ansonsten wird eine 100% ige Reflektion ausgeführt.

In entweder "v1" oder "v2" können Sie tun dies mit:

Serializer.PrepareSerializer<Person>(); 

Nachdem dies erledigt ist, die Zahlen I (aus dem Code in Ihrer E-Mail bekommen, ich habe nicht geprüft, ob das obige ist die gleiche Probe):

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 

Der andere Faktor ist die Wiederholungen; 3-10ms ist ehrlich gesagt nichts; Sie können Zahlen auf dieser Ebene nicht vergleichen. Upping es zu wiederholen, 5000-mal (Wiederverwendung der XmlSerializer/BinaryFormatter Instanzen, keine falschen Kosten eingeführt) erhalte ich:

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 

diese Einnahme zu sillier Extremen (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 

So letztlich:

  • , wenn Sie so gut wie keine Daten serialisiert werden, werden die meisten Ansätze sehr schnell sein (einschließlich protobuf-net)
  • Wenn Sie Daten hinzufügen, werden die Unterschiede deutlicher. protobuf zeichnet sich hier in der Regel entweder für einzelne große Graphen, oder viele kleine Graphen

Beachten Sie, dass auch in „v2“ das kompilierte Modell vollständig statisch kompiliert werden kann (auf eine DLL, die Sie bereitstellen können), zu entfernen sogar die (schon kleinen) Spin-up-Kosten.

+0

Marc, absolut! 'protobuf-serialisierte' Dateien sind von geringerer Größe und wenn Sie nach einer größeren Anzahl von Dateien suchen, ist die Zeit wesentlich kürzer als die der Binärdateien. Vielen Dank für Ihre Zeit. :-) –

+0

Denkst du, dass das auch mit Deserialisierungszeiten ergänzt werden kann? Es zählt auch. –

+0

@David Ich bin nicht an einem PC, aber "sehr schnell" ist ziemlich nah :) Wenn Sie * verzweifelt * das *** für dieses Beispiel wollen *** Ich kann es tun, aber ich habe unzählige andere vorhandene Maßnahmen alle sagen "schnell", in Zahlen ausgedrückt - jeder von denen? Natürlich wären Ihre eigenen Daten sogar noch überzeugender. –

5

Ich habe eine etwas andere Meinung als die markierte Antwort.Ich denke, die Zahlen aus diesen Tests spiegeln den Metadaten-Overhead von Binärformatierer wider. BinaryFormatter schreibt zuerst Meta-Daten über die Klasse, bevor Daten geschrieben werden, während Protobuf nur Daten schreibt.

Für das sehr kleine Objekt (ein Person-Objekt) in Ihrem Test wiegen die Metadaten-Kosten des binären Formatierer mehr als echte Fälle, weil es mehr Metadaten als Daten schreibt. Wenn Sie also die Anzahl der Wiederholungen erhöhen, sind die Metadatenkosten übertrieben, bis auf das gleiche Niveau wie bei der XML-Serialisierung im Extremfall.

Wenn Sie ein Person-Array serialisieren und das Array groß genug ist, sind die Metadatenkosten für die Gesamtkosten trivial. Dann sollte der binäre Formatierer ähnlich wie der Protobuf für Ihren extremen Wiederholungstest funktionieren.

PS: Ich habe diese Seite gefunden, weil ich verschiedene Serialisierer auswerte. Ich fand auch einen Blog http://blogs.msdn.com/b/youssefm/archive/2009/07/10/comparing-the-performance-of-net-serializers.aspx, der Testergebnis zeigt, dass DataContractSerializer + binary XmlDictionaryWriter mehrere Male besser als Binärformatierer durchführt. Es wurde auch mit sehr kleinen Daten getestet. Als ich den Test selbst mit großen Daten durchführte, war ich überrascht, dass das Ergebnis sehr unterschiedlich war. Testen Sie also mit echten Daten, die Sie tatsächlich verwenden.

+0

+1 Dudu, ich werde es mir ansehen. –

4

Wir serialisiert ziemlich große Objekte (etwa 50 Objekte) ständig, so habe ich einen kleinen Test geschrieben BinaryFormatter und protobuf-net zu vergleichen, wie Sie getan haben und hier sind meine Ergebnisse (10000 Objekte):

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

Das ist offensichtlich ein sehr auffälliger Unterschied. Es ist auch viel schneller beim zweiten Lauf (die Tests sind genau gleich) als beim ersten.

Aktualisierung. Doing RuntimeTypeModel.Add..Compile erzeugt folgende Ergebnisse:

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

Wenn wir im Speicher vergleichen, werden hartcodierte Serialisierung sein ganz schneller in einigen Situationen. Wenn Ihre Klasse einfach, wird vielleicht besser Sie Ihre eigenen Serializer schreiben ...

Leicht modifizierte Code:

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

und meine Ergebnisse:

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