2010-06-03 9 views
8

Napisałem program do serializacji klasy "Person" przy użyciu XMLSerializer, BinaryFormatter i ProtoBuf. Myślałem, że protobuf-net powinien być szybszy od pozostałych dwóch. Serializacja Protobuf była szybsza niż XMLSerialization, ale znacznie wolniejsza od serializacji binarnej. Czy moje zrozumienie jest nieprawidłowe? Proszę, pomóż mi to zrozumieć. Dziękuję za pomoc.protobuf-net NIE działa szybciej niż serialowanie binarne?

EDYCJA: - Zmieniłem kod (zaktualizowany poniżej), aby zmierzyć czas tylko dla serializacji i nie tworząc strumieni i nadal widzę różnicę. Czy możesz mi powiedzieć, dlaczego?

Poniżej znajduje się wyjście: -

Osoba został stworzony przy użyciu buforu protokołu w 347 milisekund

Osoba został stworzony przy użyciu języka XML w 1462 milisekund

Osoba został stworzony przy użyciu binarnego w 2 milisekundy

Kod poniżej

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

kilka szybkich notatek - Najpierw spróbuj zmniejszyć wpływ czynników zewnętrznych na twój test. Serialize do strumienia pamięci lub innego względnie względnie neutralnego celu, a nie systemu plików. Po drugie, powinieneś tylko wykonać operację serializacji - nie włączaj tworzenia strumieni lub konstrukcji obiektów. Po trzecie, powtórz testy w rozsądnej liczbie razy i zgłoś zagregowane wyniki. –

+0

Dzięki za komentarze. Wspomniałeś "raczej cel neutralny pod względem wydajności niż system plików". Co to znaczy? czy mógłbyś podać przykłady "względnie neutralnego celu"? Dziękuję Ci. –

+1

@Ashish - Myślałem głównie o strumieniu pamięci. Środowisko * może * nadal * wpływać na twoje testy, jeśli serializujesz do strumienia pamięci (na przykład presja pamięci może zmusić Cię do przejścia do pamięci wirtualnej dla jednego testu, a nie dla drugiego), ale myślę, że byłby mniej prawdopodobny wpływ na twoje wyniki niż system plików. Z perspektywy czasu ** ważniejsze jest, aby powtórzyć swoje testy, niż próbować uzyskać absolutnie neutralne warunki testowe **, ale dążenie do tych warunków nie zaszkodzi. ;) –

Odpowiedz

23

Odpowiedziałem na twój e-mail; Nie zdawałem sobie sprawy, że opublikowałeś to tutaj. Pierwsze pytanie, jakie mam, to: która wersja protobuf-net? Powodem, dla którego pytam, jest to, że bagażnik "v2" rozmyślnie kompiluje auto-kompilację, dzięki czemu mogę używać testów jednostkowych do testowania zarówno wersji uruchomieniowej, jak i wstępnie skompilowanych. Więc jeśli używasz "v2" (dostępne tylko w źródle), musisz powiedzieć, żeby skompilował model - w przeciwnym razie działa 100% odbicie.

W obu „v1” lub „v2” można to zrobić:

Serializer.PrepareSerializer<Person>(); 

Uczyniwszy to, liczb, które uzyskuję (od kodu w wiadomości e-mail, a ja nie sprawdziłem, czy powyższa jest tą samą próbką):

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 

Innym czynnikiem są powtórzenia; 3-10ms to szczerze nic; nie możesz porównywać liczb na tym poziomie. Upping go powtórzyć 5000 razy (ponownie stosując XmlSerializer/BinaryFormatter instancji, bez fałszywych kosztów wprowadzonych) uzyskać:

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 

Biorąc to głupsze skrajności (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 

więc ostatecznie:

  • gdy masz praktycznie żadnych danych do serializacji, większość podejścia będzie bardzo szybko (w tym protobuf netto)
  • w miarę dodawania danych różnice stają się bardziej oczywiste; protobuf ogólnie sprawdza się tutaj, albo dla pojedynczych dużych wykresów, albo wiele małych wykresów

Należy również zauważyć, że w "v2" skompilowany model może być w pełni skompilowany statycznie (do dll, który można wdrożyć), usuwając nawet (już małe) koszty początkowe.

+0

Marc, absolutnie! Pliki "protobuf-serialized" mają mniejszy rozmiar, a gdy testujesz większą liczbę plików, czas jest znacznie mniejszy niż binarny. Dziękuję za Twój czas. :-) –

+0

Myślisz, że możesz to uzupełnić również czasem deserializacji? To też ma znaczenie. –

+0

@David Nie jestem na PC, ale "bardzo szybko" jest całkiem blisko :) Jeśli * desperacko * chcesz, że *** dla tego przykładu *** mogę to zrobić, ale mam niezliczoną ilość innych istniejących miar, które wszyscy mówią "szybko", w kategoriach liczbowych - czy któryś z nich działa? Oczywiście, twoje własne dane byłyby jeszcze bardziej atrakcyjne –

5

Mam nieco inne zdanie niż zaznaczona odpowiedź.Myślę, że liczby z tych testów odzwierciedlają metadane związane z formatowaniem binarnym. BinaryFormatter zapisuje metadane o klasie przed zapisaniem danych, podczas gdy protobuf zapisuje tylko dane.

W przypadku bardzo małego obiektu (jeden obiekt Person) w teście koszt metadanych binarnego formatyzatora waży więcej niż rzeczywiste przypadki, ponieważ zapisuje więcej metadanych niż danych. Tak więc, kiedy zwiększysz liczbę powtórzeń, koszt metadanych jest przesadzony, aż do poziomu, w jakim jest serializacja xml w skrajnym przypadku.

Jeśli serializujesz tablicę Person, a tablica jest wystarczająco duża, koszt meta-danych będzie banalny do całkowitego kosztu. Następnie program do formatowania binarnego powinien działać podobnie jak protobuf do ekstremalnego testu powtórzeń.

PS: Znalazłem tę stronę, ponieważ oceniam różne serializery. Znalazłem też blog http://blogs.msdn.com/b/youssefm/archive/2009/07/10/comparing-the-performance-of-net-serializers.aspx, który pokazuje wynik testu, że DataContractSerializer + binarny XmlDictionaryWriter wykonuje kilka razy lepiej niż binarny formatter. Testowano również z bardzo małymi danymi. Kiedy sam testowałem z dużymi danymi, zdziwiłem się, gdy okazało się, że wynik jest zupełnie inny. Wykonaj test z rzeczywistymi danymi, których faktycznie użyjesz.

+0

+1 Dudu, przyjrzę się. –

4

Mamy serializacji dość dużych obiektów (około 50 nieruchomości) stale, więc napisałem mały test porównania BinaryFormatter i protobuf netto, tak samo, jak i tutaj są moje wyniki (10000 obiektów):

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

To oczywiście bardzo zauważalna różnica. Jest także znacznie szybszy w drugim biegu (testy są dokładnie takie same), jak w przypadku pierwszego.

Aktualizacja. Doing RuntimeTypeModel.Add..Compile generuje następujące wyniki:

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

Jeśli porównamy w pamięci zakodowane serializacji będzie dość szybciej w niektórych sytuacjach. Jeśli klasa prosty, może lepiej będzie napisać własne serializatora ...

nieco zmodyfikowany kod:

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

i moje wyniki:

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 
Powiązane problemy