Pruebas de rendimiento de serializaciones utilizadas por enlaces WCF

Tengo el siguiente objeto:

public partial class Game { public bool Finished { get; set; } public Guid GameGUID { get; set; } public long GameID { get; set; } public bool GameSetup { get; set; } public Nullable MaximumCardsInDeck { get; set; } public Player Player { get; set; } public Player Player1 { get; set; } public bool Player1Connected { get; set; } public bool Player1EnvironmentSetup { get; set; } public long Player1ID { get; set; } public int Player1Won { get; set; } public bool Player2Connected { get; set; } public bool Player2EnvironmentSetup { get; set; } public long Player2ID { get; set; } public int Player2Won { get; set; } public int Round { get; set; } public Nullable RoundsToWin { get; set; } public bool Started { get; set; } public string StateXML { get; set; } public Nullable TimeEnded { get; set; } public Nullable TimeLimitPerTurn { get; set; } public byte[] TimeStamp { get; set; } public Nullable TimeStarted { get; set; } } 

Esta clase se completará con algunos datos de prueba .

Necesito comparar el rendimiento de los diferentes serializadores utilizados por las diferentes formas de enlaces para WCF Services:

  • basicHttpBinding => SoapFormatter (TextFormatter?)
  • binaryBinding => BinaryFormatter
  • XMLFormatter

Lo que necesito hacer en detalle es:

  • Obtenga ahora el tamaño del objeto que se serializa
  • Obtenga ahora el tamaño después de la serización
  • Hora de serializar
  • Tiempo para deserializar

Ya probé algunas cosas, pero estoy luchando un poco. Quizás ya haya algún código simple para este tipo de medición.

DE ACUERDO; Morderé … aquí hay algunas métricas de serializador en bruto (énfasis: es posible que necesite considerar base-64 / MTOM para obtener los requisitos generales de ancho de banda, más los gastos generales fijos (tanto de espacio como de CPU) que WCF agrega), sin embargo; resultados primero:

 BinaryFormatter Length: 1314 Serialize: 6746 Deserialize: 6268 XmlSerializer Length: 1049 Serialize: 3282 Deserialize: 5132 DataContractSerializer Length: 911 Serialize: 1411 Deserialize: 4380 NetDataContractSerializer Length: 1139 Serialize: 2014 Deserialize: 5645 JavaScriptSerializer Length: 528 Serialize: 12050 Deserialize: 30558 (protobuf-net v2) Length: 112 Serialize: 217 Deserialize: 250 

(Así que concluyo protobuf-net v2 el ganador …)

Números actualizados con .NET 4.5 y comstackciones actuales de la biblioteca, en una máquina más nueva:

 BinaryFormatter Length: 1313 Serialize: 2786 Deserialize: 2407 XmlSerializer Length: 1049 Serialize: 1265 Deserialize: 2165 DataContractSerializer Length: 911 Serialize: 574 Deserialize: 2011 NetDataContractSerializer Length: 1138 Serialize: 850 Deserialize: 2535 JavaScriptSerializer Length: 528 Serialize: 8660 Deserialize: 8468 (protobuf-net v2) Length: 112 Serialize: 78 Deserialize: 134 

con plataforma de prueba (comstackda con optimizaciones, ejecute en línea de comandos):

(y nótese que tuve que inventar la clase Player y algunos datos de muestra):

 using System; using System.Diagnostics; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Web.Script.Serialization; using System.Xml.Serialization; using ProtoBuf.Meta; static class Program { static void Main() { var orig = new Game { Finished = true, GameGUID = Guid.NewGuid(), GameID = 12345, GameSetup = false, MaximumCardsInDeck = 20, Player = new Player { Name = "Fred"}, Player1 = new Player { Name = "Barney"}, Player1Connected = true, Player1EnvironmentSetup = true, Player1ID = 12345, Player1Won = 3, Player2Connected = true, Player2EnvironmentSetup = true, Player2ID = 23456, Player2Won = 0, Round = 4, RoundsToWin = 5, Started = true, StateXML = "not really xml", TimeEnded = null, TimeLimitPerTurn = 500, TimeStamp = new byte[] {1,2,3,4,5,6}, TimeStarted = DateTime.Today}; const int LOOP = 50000; GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); using (var ms = new MemoryStream()) { var ser = new BinaryFormatter(); Console.WriteLine(); Console.WriteLine(ser.GetType().Name); ser.Serialize(ms, orig); Console.WriteLine("Length: " + ms.Length); ms.Position = 0; ser.Deserialize(ms); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ms.SetLength(0); ser.Serialize(ms, orig); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ser.Deserialize(ms); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); using (var ms = new MemoryStream()) { var ser = new XmlSerializer(typeof(Game)); Console.WriteLine(); Console.WriteLine(ser.GetType().Name); ser.Serialize(ms, orig); Console.WriteLine("Length: " + ms.Length); ms.Position = 0; ser.Deserialize(ms); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ms.SetLength(0); ser.Serialize(ms, orig); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ser.Deserialize(ms); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); using (var ms = new MemoryStream()) { var ser = new DataContractSerializer(typeof(Game)); Console.WriteLine(); Console.WriteLine(ser.GetType().Name); ser.WriteObject(ms, orig); Console.WriteLine("Length: " + ms.Length); ms.Position = 0; ser.ReadObject(ms); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ms.SetLength(0); ser.WriteObject(ms, orig); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ser.ReadObject(ms); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); using (var ms = new MemoryStream()) { var ser = new NetDataContractSerializer(); Console.WriteLine(); Console.WriteLine(ser.GetType().Name); ser.Serialize(ms, orig); Console.WriteLine("Length: " + ms.Length); ms.Position = 0; ser.Deserialize(ms); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ms.SetLength(0); ser.Serialize(ms, orig); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ser.Deserialize(ms); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); { var sb = new StringBuilder(); var ser = new JavaScriptSerializer(); Console.WriteLine(); Console.WriteLine(ser.GetType().Name); ser.Serialize(orig, sb); Console.WriteLine("Length: " + sb.Length); ser.Deserialize(sb.ToString(), typeof(Game)); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { sb.Length = 0; ser.Serialize(orig, sb); } watch.Stop(); string s = sb.ToString(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ser.Deserialize(s, typeof(Game)); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); using (var ms = new MemoryStream()) { var ser = CreateProto(); Console.WriteLine(); Console.WriteLine("(protobuf-net v2)"); ser.Serialize(ms, orig); Console.WriteLine("Length: " + ms.Length); ms.Position = 0; ser.Deserialize(ms, null, typeof(Game)); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ms.SetLength(0); ser.Serialize(ms, orig); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { ms.Position = 0; ser.Deserialize(ms, null, typeof(Game)); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } Console.WriteLine(); Console.WriteLine("All done; any key to exit"); Console.ReadKey(); } static TypeModel CreateProto() { var meta = TypeModel.Create(); meta.Add(typeof(Game), false).Add(Array.ConvertAll(typeof(Game).GetProperties(),prop=>prop.Name)); meta.Add(typeof(Player), false).Add(Array.ConvertAll(typeof(Player).GetProperties(),prop=>prop.Name)); return meta.Compile(); } } [Serializable, DataContract] public partial class Game { [DataMember] public bool Finished { get; set; } [DataMember] public Guid GameGUID { get; set; } [DataMember] public long GameID { get; set; } [DataMember] public bool GameSetup { get; set; } [DataMember] public Nullable MaximumCardsInDeck { get; set; } [DataMember] public Player Player { get; set; } [DataMember] public Player Player1 { get; set; } [DataMember] public bool Player1Connected { get; set; } [DataMember] public bool Player1EnvironmentSetup { get; set; } [DataMember] public long Player1ID { get; set; } [DataMember] public int Player1Won { get; set; } [DataMember] public bool Player2Connected { get; set; } [DataMember] public bool Player2EnvironmentSetup { get; set; } [DataMember] public long Player2ID { get; set; } [DataMember] public int Player2Won { get; set; } [DataMember] public int Round { get; set; } [DataMember] public Nullable RoundsToWin { get; set; } [DataMember] public bool Started { get; set; } [DataMember] public string StateXML { get; set; } [DataMember] public Nullable TimeEnded { get; set; } [DataMember] public Nullable TimeLimitPerTurn { get; set; } [DataMember] public byte[] TimeStamp { get; set; } [DataMember] public Nullable TimeStarted { get; set; } } [Serializable, DataContract] public class Player { [DataMember] public string Name { get; set; } } 

También tengo gráficos de referencia para diferentes serializadores en .NET que muestran el serializador binario protobuf-net de @Marc Gravell como el claro ganador. Aunque mantengo los serializadores de texto más rápidos .NET que están más cerca de igualarlo y también son mucho más rápidos que todos los serializadores que vienen en BCL en .NET.

Estos puntos de referencia se basan en la base de datos de muestra de Nortwind de Microsoft y muestran cuánto más lento se compara cada serializador con el de Protobuf-net.

 ProtoBuf.net(v1) 1x ServiceStack TypeSerializer 2.23x ServiceStack JsonSerializer 2.58x Microsoft DataContractSerializer 6.93x NewtonSoft.Json 7.83x Microsoft BinaryFormatter 9.21x Microsoft JsonDataContractSerializer 9.31x 

Los puntos de referencia completos están disponibles aquí

Entonces, si prefiere / necesito usar un serializador de texto rápido, aquí hay enlaces a los serializadores de texto de fuente abierta de Service Stack :

  • Serializador JSON
  • Serializador JSV

Por cierto, el JavaScriptSerializer de Microsoft mostró el peor rendimiento y en ocasiones fue 40x-100x más lento que las redes de protobuf. Lo hice porque estaban ralentizando mis puntos de referencia 🙂

Modifiqué el código fuente de referencia de @ Marc y agregué los resultados para los Serializadores JSV y JSON de ServiceStack. Estos son los resultados de mi 3yo iMac:

 BinaryFormatter Length: 1313 Serialize: 3959 Deserialize: 3395 XmlSerializer Length: 1049 Serialize: 1710 Deserialize: 2716 DataContractSerializer Length: 911 Serialize: 712 Deserialize: 2117 NetDataContractSerializer Length: 1138 Serialize: 1093 Deserialize: 4825 TypeSerializer Length: 431 Serialize: 496 Deserialize: 887 JsonSerializer Length: 507 Serialize: 558 Deserialize: 1213 

Aquí está el código fuente que agregué al punto de referencia de @ Marc más arriba.

 GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); var sbJsv = new StringBuilder(4096); using (var sw = new StringWriter(sbJsv)) { Console.WriteLine(); Console.WriteLine(typeof(TypeSerializer).Name); TypeSerializer.SerializeToWriter(orig, sw); var jsv = sbJsv.ToString(); Console.WriteLine("Length: " + sbJsv.Length); TypeSerializer.DeserializeFromString(jsv); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { sbJsv.Length = 0; TypeSerializer.SerializeToWriter(orig, sw); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { TypeSerializer.DeserializeFromString(jsv); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); var sbJson = new StringBuilder(4096); using (var sw = new StringWriter(sbJson)) { Console.WriteLine(); Console.WriteLine(typeof(JsonSerializer).Name); JsonSerializer.SerializeToWriter(orig, sw); var json = sbJson.ToString(); Console.WriteLine("Length: " + sbJson.Length); JsonSerializer.DeserializeFromString(json); var watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { sbJson.Length = 0; JsonSerializer.SerializeToWriter(orig, sw); } watch.Stop(); Console.WriteLine("Serialize: " + watch.ElapsedMilliseconds); watch = Stopwatch.StartNew(); for (int i = 0; i < LOOP; i++) { JsonSerializer.DeserializeFromString(json); } watch.Stop(); Console.WriteLine("Deserialize: " + watch.ElapsedMilliseconds); } 

Nota: No pude acceder a los archivos de protobuf-net v2 r352 de @ Marc que usó para esto, así que tuve que comentar los puntos de referencia de protobuf-net.

En el nivel más simple; simplemente serialice una carga de datos, cuéntela y mida el ancho de banda. Y shed-load debería incluir cargas útiles grandes y pequeñas (pero muchas).

También debe considerar con / sin MTOM. Y aunque quizás sea parcial, sugiero que incluya serializadores WCF alternativos como protobuf-net (avíseme si necesita ayuda para conectarlo). De mucho trabajo en el área, generalmente derrota a todos los que ha mencionado por un margen decente en cada medida.

Gran parte de lo que está involucrado aquí se puede investigar en el nivel de serializador sin siquiera tocar WCF, sin embargo, eso pasa por alto base-64 / MTOM, por lo que no es una imagen al 100 por ciento.

Sin embargo, no podemos definir sus medidas para usted; solo tú puedes decidir qué es la clave. Sin embargo, tengo varias medidas: por lo general, simplemente:

  • serialize una vez a MemorySteam (y deserialize); esto te da el tamaño y prepara el JIT
  • ahora mantén ese flujo de memoria (como un búfer útil) y (dentro del cronómetro) serializa muchos miles si hay veces. Dividir. Rebobine cada vez para que sobrescriba (sin extender).
  • repite pero deserializa miles de veces. Dividir.

Use un objeto de tamaño constante; obtener información de “tamaño” de un Tipo es desordenado y no le hará ganar mucho en términos de averiguar cuál es el “mejor”. Cualquier objeto decorado como DataContract se puede serializar a binario (DataContract hereda Serializable), XML básico (cualquier objeto con un constructor predeterminado se puede serializar en XML) o DataContract XML (esto requiere la mayor cantidad de marcas para comenzar, pero es bastante simple) .

Para una prueba en ejecución, crea un método que tome un objeto y un serializador. Debería crear un MemoryStream e iniciar un cronómetro, luego serializar el objeto en el MemoryStream (asegúrese de vaciar ()). Luego detiene el cronómetro y le muestra los resultados como un TimeSpan y la longitud del Stream. Luego restablece e inicia el cronómetro y deserializa el flujo, y recuerda esa hora. Puede establecer los resultados de retorno como una estructura simple.

Ejecute esto con el mismo objeto para cada serializador que quiera probar. Imprima cada uno de los resultados en la consola o depure la salida, y gane el mejor serializador.

En general, creo que encontrarás:

  • BinarySerializer será más rápido y más pequeño, ya que tiene la menor sobrecarga de bytes para escribir durante la serialización. Sin embargo, las serializaciones binarias de .NET son específicas de la plataforma; si desea hablar con cualquier cosa que no sea otro ensamblado .NET que sepa sobre su tipo exacto, olvídelo.

  • XMLSerializer, SoapSerializer y DataContractSerializer generan varias formas de XML. DataContract es en realidad el formato más simple (el XML es extremadamente básico porque el protocolo de enlace y otra información de protocolo / comunicación es independiente) y probablemente sea bastante rápido. SOAP tiene mucha hinchazón en el archivo serializado debido a la información de transporte y metadatos, pero es fácil de generar ya que es un formato bastante estricto. La serialización básica de XML, debido a que es muy flexible, tiene una gran sobrecarga, pero puede generar un esquema muy simple o muy complejo.