06 February 2014

No Goldilocks Serializers

So in any architecture that uses messaging for communication, one of the key pieces is serialization. Along those lines, I tried several different serializers and benchmarked the ones that would actually work in my scenario. I borrowed a lot of the serializer test code from various examples on stack overflow (e.g. this one).

Here are ones that I tried:

protobuf-net
Avro
MsgPack
fastJSON
XSockets (ServiceStack.Text)
JSON.NET

I didn't test ServiceStack on purpose, but instead was using the XSockets serializer, which turned out to just be ServiceStack. I didn't care to test ServiceStack at first because of its AGPL license, which is incompatible with my desired freedoms. The XSockets version of ServiceStack is licensed differently as part of the XSockets project. (Since they don't mention AGPL in their license page.)

Here are my requirements for messages I want to serialize:
  1. No attributes (not even Serializable in my tests)
  2. All public members are read only
These requirements killed Avro, MsgPack, and fastJSON right out of the gate. The .NET libraries for Avro and MsgPack wouldn't even instantiate a serializer. fastJSON crashed when trying to serialize. I didn't investigate why.

To get it to work with the XSockets JsonSerializer (which comes with XSockets Core on nuget and again is just ServiceStack.Text), I had to change my readonly fields to properties with private setters. Apparently it ignores fields, even public ones. Later, I figured out how to make it pick up fields, but it didn't really affect the test.

For both ServiceStack and JSON.Net, I serialized to/from an interface to force it to embed the type. This is important for my use case.

Protobuf-net doesn't support type embedding per se, so I had to send two messages. The first (header) message contained the type of the subsequent message. This test includes the time it took protobuf-net to serialize and deserialize both messages for fairness. I do know about the prefix in which I can send an integer to represent the message type, but try consistently hashing and arbitrary type to the same integer value and tell me how that works for you...

Protobuf-net has the added wrinkle that I have to dynamically add the classes to its serializer at runtime (remember, no attributes). This is a huge negative. It's not too bad for simple messages, but if you have any polymorphic properties, this code could get complicated. E.g. Find all inheritors for this type; Find all implementors of this interface; Oh, and if those objects also have reference type properties, recurse. It would be so much easier if I could just embed the type Marc... not that you will ever see this. I write this for myself, you know.

Ultimately, my conclusion for messaging is that there's not an absolute win for any one serializer. Nothing was "just right". Protobuf-net  was the fastest, but the hardest to deal with. ServiceStack.Text is the fastest JSON serializer for my use case, but the AGPL license destroys too many of my freedoms. JSON.Net performed the poorest (but still light years ahead of the default .NET serializers). Deserialization was its weakness. For now I will try out the XSockets version of ServiceStack.Text since I'm planning on using it with XSockets anyway, and I can actually stomach the XSocket license it comes under (BSD 2-clause).

Below are the results for 100000 iterations.

protobuf-net (header + message)
Length: 285
Serialize: 213
Deserialize: 897

XSockets Serializer
Length: 508
Serialize: 933
Deserialize: 1606

JSON.Net BSON
Length: 404
Serialize: 1127
Deserialize: 3417

JSON.Net JSON String
Length: 540
Serialize: 928
Deserialize: 3644

Here are the classes used:

    public class Header
    {
        public Type T { get; private set; }
        public Header(Type t)
        {
            T = t;
        }

    }

    public class TestMessage : IMessage

    {
        public Guid SenderId { get; private set; }
        public Guid ReferenceId { get; private set; }
        public Guid OrderId { get; private set; }
        public Guid CustomerId { get; private set; }
        public LineItem[] LineItems { get; private set; }
        public DateTime OrderedOn { get; private set; }
        public TestMessage(Guid senderId, Guid referenceId, Guid orderId, Guid customerId, LineItem[] lineItems, DateTime orderedOn)
        {
            SenderId = senderId;
            ReferenceId = referenceId;
            OrderId = orderId;
            CustomerId = customerId;
            LineItems = lineItems;
            OrderedOn = orderedOn;
        }
    }

    public class LineItem

    {
        public Guid ProductId { get; private set; }
        public int Quantity { get; private set; }
        public LineItem(Guid productId, int quantity)
        {
            ProductId = productId;
            Quantity = quantity;
        }

    }

    public interface IMessage { }