2010-06-03 17 views
8

Escribí un programa para serializar una clase 'Persona' utilizando XMLSerializer, BinaryFormatter y ProtoBuf. Pensé que protobuf-net debería ser más rápido que los otros dos. La serialización de Protobuf fue más rápida que XMLSerialization pero mucho más lenta que la serialización binaria. ¿Es mi entendimiento incorrecto? Por favor hazme entender esto Gracias por la ayuda.protobuf-net ¿NO es más rápido que la serialización binaria?

EDITAR: - Cambié el código (actualizado a continuación) para medir el tiempo solo para la serialización y no crear las secuencias y seguir viendo la diferencia. ¿Podría uno decirme por qué?

A continuación se presenta la salida: -

persona recibió creado usando búfer de protocolo en 347 milisegundos

persona recibió creado usando XML en 1462 milisegundos

persona recibió creado usando binario en 2 milisegundos

Código debajo de

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

Unas pocas notas rápidas - en primer lugar, tratar de reducir la influencia de factores externos en su prueba. Serializar en una secuencia de memoria u otro objetivo relativamente neutral en relación con el rendimiento del sistema de archivos. En segundo lugar, solo debe medir el tiempo de la operación de serialización; no incluya la creación de las secuencias o la construcción de objetos. Tercero, repita sus pruebas un número razonable de veces e informe los resultados agregados. –

+0

Gracias por los comentarios. Usted mencionó "objetivos relativamente neutrales en cuanto al rendimiento en lugar del sistema de archivos". Qué significa eso? ¿podría darnos algunos ejemplos de un "objetivo relativamente neutral respecto al rendimiento"? Gracias. –

+1

@Ashish - Estaba pensando principalmente en un flujo de memoria. El entorno * podría * Todavía afectar sus pruebas si serializa a una secuencia de memoria (por ejemplo, la presión de memoria te puede obligar a ir a la memoria virtual para una prueba y no el otro), pero creo que sería menos probable que influyen en su resultados que el sistema de archivos. En retrospectiva, ** probablemente sea más importante repetir las pruebas que tratar de obtener condiciones de prueba absolutamente neutras **, pero luchar por esas condiciones no hará daño. ;) –

Respuesta

23

He respondido a su correo electrónico; No me di cuenta de que también lo habías publicado aquí. La primera pregunta que tengo es: ¿qué versión de protobuf-net? La razón por la que pregunto es que el tronco de desarrollo de "v2" ha desactivado la compilación automática deliberadamente, por lo que puedo usar mis pruebas unitarias para probar tanto el tiempo de ejecución como las versiones compiladas previamente. Por lo tanto, si está utilizando "v2" (solo disponible en la fuente), debe indicarle que compile el modelo; de lo contrario, ejecuta una reflexión del 100%.

En cualquiera de los "v1" o "v2" se puede hacer esto con:

Serializer.PrepareSerializer<Person>(); 

Una vez hecho esto, los números que recibo (del código en su correo electrónico; no he comprobado si el anterior es el mismo ejemplo):

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 

El otro factor es las repeticiones; 3-10ms es francamente nada; no puedes comparar números alrededor de este nivel. Subiendo a repetir 5000 veces (la reutilización de los XmlSerializer/BinaryFormatter casos; sin costes falsas introducidas) me sale:

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 

Teniendo esto a los extremos más tontas (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 

Así que en última instancia:

  • cuando se tiene prácticamente ningún dato para serializar, la mayoría de los enfoques serán muy rápido (incluyendo protobuf-net)
  • a medida que agrega datos, las diferencias se vuelven más obvias; protobuf generalmente sobresale aquí, ya sea para grandes gráficos individuales, o un montón de pequeños gráficos

Tenga en cuenta también que en "v2" el modelo compilado puede ser totalmente estática-compilado (a un DLL que se puede implementar), eliminando incluso los costos de giro (ya de por sí pequeños).

+0

Marc, ¡absolutamente! Los archivos "protobuf serializados" son de menor tamaño y cuando prueba una cantidad mayor de archivos, el tiempo es significativamente menor que el binario. Gracias por tu tiempo. :-) –

+0

¿Crees que ustedes pueden complementar esto con los tiempos de deserialización también? También es importante. –

+0

@David No estoy en una PC, pero "muy rápido" es bastante cercano :) Si * desparately * quiere ese *** para este ejemplo *** puedo hacerlo, pero tengo muchas otras medidas existentes que todos dicen "rápido", en términos numéricos, ¿alguno de ellos lo hace? Por supuesto, su propia información sería aún más convincente –

5

Tengo una opinión ligeramente diferente a la respuesta marcada.Creo que los números de estas pruebas reflejan la sobrecarga de metadatos del formateador binario. BinaryFormatter escribe metadatos sobre la clase primero antes de escribir datos, mientras que protobuf solo escribe datos.

Para el objeto muy pequeño (un objeto Persona) en su prueba, el costo de los metadatos del formateador binario pesa más que los casos reales, porque está escribiendo más metadatos que datos. Por lo tanto, cuando aumenta el recuento de repeticiones, el costo de los metadatos se exagera, hasta el mismo nivel que la serialización xml en casos extremos.

Si serializar una matriz persona, y el conjunto es lo suficientemente grande, entonces el costo de meta-datos será trivial para el costo total. Entonces el formateador binario debe realizar un funcionamiento similar al de protobuf para su prueba de repetición extrema.

PD: He encontrado esta página porque yo estoy evaluando diferentes serializadores. También encontré un blog http://blogs.msdn.com/b/youssefm/archive/2009/07/10/comparing-the-performance-of-net-serializers.aspx que muestra resultados de la prueba que DataContractSerializer + XmlDictionaryWriter binaria realiza varias veces mejor que el formateador binario. También se probó con datos muy pequeños. Cuando realicé la prueba con grandes datos, me sorprendió descubrir que el resultado era muy diferente. Así que prueba con datos reales que realmente usarás.

+0

+1 Dudu, voy a echar un vistazo. –

4

Nos serializar objetos muy grandes (alrededor de 50 unidades) constantemente, por lo que he escrito una pequeña prueba para comparar BinaryFormatter y protobuf red, tal como lo hizo y aquí están mis resultados (10000): objetos

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

Eso es, obviamente, una diferencia muy notable. También es mucho más rápido en la segunda ejecución (las pruebas son exactamente las mismas) que en la primera.

Actualización. Haciendo RuntimeTypeModel.Add..Compile genera resultados siguientes:

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

Si comparamos en la memoria, la serialización modificable será bastante más rápido en algunas situaciones. Si su clase simple, tal vez mejor escribirá su propio serializador ...

código ligeramente modificado:

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

y mis resultados:

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 
Cuestiones relacionadas