Search code examples
c#.netwcfperformancewcf-binding

Performance Tests of Serializations used by WCF Bindings


I have the following 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<int> 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<int> RoundsToWin { get; set; }

    public bool Started { get; set; }

    public string StateXML { get; set; }

    public Nullable<DateTime> TimeEnded { get; set; }

    public Nullable<int> TimeLimitPerTurn { get; set; }

    public byte[] TimeStamp { get; set; }

    public Nullable<DateTime> TimeStarted { get; set; }    
}

This class gonna be filled with some test data.

I need the to compare the Performance of different Serializers used by the different forms of bindings for WCF Services:

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

What i need to do in detail is:

  • Get to now the size of the Object being serialized
  • Get to now the size after serizlization
  • Time to serialize
  • Time to deserialize

I already tried some stuff, but i am struggling a bit. Maybe there is already some simple code for this kind of measurement.


Solution

  • OK; I'll bite... here's some raw serializer metrics (emph: you may need to consider base-64/MTOM to get overall bandwidth requirements, plus whatever fixed overheads (both space and CPU) that WCF adds), however; results first:

    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
    

    (so I conclude protobuf-net v2 the winner...)

    Numbers updated with .NET 4.5 and current library builds, on a newer machine:

    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
    

    with test rig (compiled with optimizations, run at command line):

    (and note I had to invent the Player class and some sample data):

    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<int> 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<int> RoundsToWin { get; set; }
        [DataMember]
        public bool Started { get; set; }
        [DataMember]
        public string StateXML { get; set; }
        [DataMember]
        public Nullable<DateTime> TimeEnded { get; set; }
        [DataMember]
        public Nullable<int> TimeLimitPerTurn { get; set; }
        [DataMember]
        public byte[] TimeStamp { get; set; }
        [DataMember]
        public Nullable<DateTime> TimeStarted { get; set; }
    }
    [Serializable, DataContract]
    public class Player
    {
        [DataMember]
        public string Name { get; set; }
    }