Performance Tests of Serializations used by WCF Bindings

Ben picture Ben · Sep 24, 2010 · Viewed 14.4k times · Source

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.

Answer

Marc Gravell picture Marc Gravell · Sep 25, 2010

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