Test delle prestazioni delle serializzazioni utilizzate dalle associazioni WCF

Ho il seguente object:

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

Questa class verrà riempita con alcuni dati di test .

Ho bisogno di confrontare le prestazioni dei diversi serializzatori usati dalle diverse forms di binding per i servizi WCF:

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

Quello che devo fare in dettaglio è:

  • Ottieni ora la dimensione dell’object object di serializzazione
  • Ottenere ora le dimensioni dopo la serizlizzazione
  • Tempo di serializzare
  • È tempo di deserializzare

Ho già provato alcune cose, ma sto faticando un po ‘. Forse c’è già un codice semplice per questo tipo di misurazione.

OK; Morderò … Ecco alcune metriche di serializzazione raw (emph: potrebbe essere necessario prendere in considerazione base-64 / MTOM per ottenere i requisiti di larghezza di banda complessivi, oltre a qualsiasi sovraccarico fisso (sia spazio che CPU) aggiunto da WCF); risultati prima:

 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 

(quindi concludo protobuf-net v2 il vincitore …)

Numeri aggiornati con .NET 4.5 e versioni correnti della libreria, su una macchina più recente:

 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 il banco di prova (compilato con ottimizzazioni, eseguito a linea di comando):

(e nota che ho dovuto inventare la class Player e alcuni dati di esempio):

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

Ho anche grafici di benchmark per diversi serializzatori in .NET che mostrano il serializer binario protobuf-net di @Marc Gravell come il chiaro vincitore. Anche se mantengo i serializzatori di testo più veloci .NET che si avvicinano di più alla corrispondenza e sono anche molto più veloci di tutti i serializzatori che arrivano in BCL in .NET.

Questi benchmark si basano sul database di esempio Nortwind di Microsoft e mostrano quanto più lento ogni serializzatore è paragonato a 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 

I benchmark completi sono disponibili qui

Quindi, se preferisci / hai bisogno di usare un serial-serial veloce, qui ci sono i link ai serializzatori di testo open source di Service Stack :

  • Serializzatore JSON
  • Serializzatore JSV

Tra l’altro, JavaScriptSerializer di Microsoft ha mostrato le peggiori prestazioni e, a volte, era 40x-100x più lento delle reti protobuf. L’ho preso perché stavano rallentando i miei benchmark 🙂

Ho modificato il codice sorgente @ benchmark di Marc e ho aggiunto i risultati per i serializzatori JSV e JSON di ServiceStack Ecco i risultati sul mio iMac 3yo:

 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 

Ecco il codice sorgente che ho aggiunto al benchmark di @ Marc sopra.

 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: non sono riuscito a reperire le @ protobuf-net v2 r352 dll di Marc che ha usato per questo motivo, quindi ho dovuto commentare i benchmark protobuf-net.

Al livello più semplice; serializzate semplicemente un capannone di dati, calcolateli e misurate la larghezza di banda. E il carico del capannone dovrebbe includere sia grandi che piccoli (ma molti) carichi utili.

Si dovrebbe anche considerare con / senza MTOM. E anche se sono forse di parte, suggerisco di includere serializzatori WCF alternativi come protobuf-net (fammi sapere se hai bisogno di aiuto per farlo). Da un sacco di lavoro nella zona di solito sconfigge tutti quelli che hai citato con un margine decente su ogni misura.

Gran parte di ciò che è coinvolto qui può essere analizzato a livello di serializzatore senza nemmeno toccare WCF, tuttavia bypases base-64 / MTOM non è un’immagine al 100%.

Tuttavia, non possiamo definire le vostre misure per voi; solo tu puoi decidere qual è la chiave. Ho un certo numero di misure – di solito è semplicemente:

  • serializza una volta su MemorySteam (e deserializza); questo ti dà le dimensioni e prepara il JIT
  • ora mantieni quel stream di memoria (come un comodo buffer) e (all’interno del cronometro) serializza molte migliaia se volte. Dividere. Riavvolgi ogni volta in modo da sovrascrivere (non estendere).
  • ripetere ma deserializibg molte migliaia di volte. Dividere.

Usa un object di dimensioni costanti; ottenere informazioni di “dimensione” da un tipo è disordinato e non ti farà guadagnare molto in termini di capire qual è il “migliore”. Qualsiasi object decorato come DataContract può essere serializzato su binario (DataContract eredita Serializable), XML di base (qualsiasi object con un costruttore predefinito può essere serializzato in XML) o DataContract XML (questo richiede la maggior parte dei markup per iniziare, ma è piuttosto semplice) .

Per un test in esecuzione, creare un metodo che utilizzi un object e un serializzatore. Dovrebbe creare un MemoryStream e avviare uno Stop Watch, quindi serializzare l’object nel MemoryStream (assicurarsi di Flush ()). Quindi ferma il cronometro e restituisce i risultati come TimeSpan e la lunghezza del stream. Quindi resettare e avviare il cronometro e deserializzare il stream, e ricordare quella volta. È ansible impostare i risultati restituiti come una semplice struttura.

Esegui questo con lo stesso object per ogni serializzatore che vuoi testare. Emetti ciascuno dei risultati sull’output di console o di debug e vinca il serializzatore migliore.

In generale, penso che troverai:

  • BinarySerializer sarà il più veloce e il più piccolo, poiché ha il minor overhead di byte da scrivere durante la serializzazione. Tuttavia, le serializzazioni binarie di .NET sono specifiche della piattaforma; se vuoi parlare a qualcosa di diverso da un altro assembly .NET che conosce il tuo tipo esatto, dimenticalo.

  • XMLSerializer, SoapSerializer e DataContractSerializer generano varie forms di XML. DataContract è in realtà il formato più semplice (l’XML è estremamente semplice perché l’handshake e altre informazioni di protocollo / comunicazione sono separate) e probabilmente sarà piuttosto veloce. SOAP è molto gonfio nel file serializzato a causa delle informazioni sui trasporti e sui metadati, ma è facile da generare in quanto è un formato piuttosto rigido. Serializzazione XML di base, perché è così flessibile, ha un sacco di overhead, ma può generare uno schema molto semplice o molto complesso.