protobuf

protobuf-net v2 performance test

Posted on Updated on

This is a continuation from my previous post of Run time initialisation of protobuf-net v2 without annotation

I listed couple of ways to use protobuf-net v2:

  1. annotation on the class (ProtoContractAttribute) and property (ProtoMemberAttribute)
  2. annotation only on the class (ProtoContractAttribute) with ImplicitFields named parameter
  3. run time registration using the default model (RuntimeTypeModel.Default)
  4. run time registration with separate type model which supports run time compilation (from the method description -> Fully compiles the current model into a static-compiled serialization dll)
  5. precompiled serializer (this is not really a different method, but a further optimisation to the 1st and 2nd options above)

Based on the list above, I created some performance tests using a class defined with 150+ properties (mixed with string, custom classes) to make it closer to real life entity that I have in the project.
And below is the statistics produced by the test for different collection sizes, ordered by the most “efficient” ones to regular binary serialization for comparison purpose.

Conclusions:

  • Performance : Precompiled serializer seems to be the notable performant option, although as you can see below, the other protobuf options are not very far off from one to another as well
  • Size : ImplicitFields seems to be the smallest in size, although it’s not far off to other options, although the run time compiled model option seems to be on the higher end.

Please note that this benchmark below is purely for my curiosity purpose, please do NOT refer this as a guidelines for any of your application.
The main reason why I’m saying the above is because I actually found somewhat different statistics when I tried to run this on the real class/model being used in my project.
The performance is somewhat as below between, however the size was the quite surprising factor where the memory size for the run time registration was 50% higher than the annotation option hence we ended up sticking up with the full annotation option as size is a very important factor as we need to send the data through the wire hence any significant size increase will introduce latency.
So again, moral of the story, please ALWAYS run a benchmark using your real project before jumping into any conclusion.
Attached the sample project again below for reference as usual.

Sample Project


-------------------------------
TestProtobufPreCompiledTypeModel
100 items, Size: 238905, Completed: 30 ms, Serialization: 6 ms, Deserialization: 13 ms
1000 items, Size: 2389006, Completed: 378 ms, Serialization: 69 ms, Deserialization: 161 ms
10000 items, Size: 23890006, Completed: 4195 ms, Serialization: 675 ms, Deserialization: 1806 ms
-------------------------------
TestProtobufRuntimeRegistrationWithCompiledTypeModel
100 items, Size: 273805, Completed: 42 ms, Serialization: 7 ms, Deserialization: 24 ms
1000 items, Size: 2738006, Completed: 424 ms, Serialization: 76 ms, Deserialization: 191 ms
10000 items, Size: 27380006, Completed: 4634 ms, Serialization: 762 ms, Deserialization: 2151 ms
-------------------------------
TestProtobufRuntimeRegistration
100 items, Size: 238000, Completed: 35 ms, Serialization: 8 ms, Deserialization: 16 ms
1000 items, Size: 2380000, Completed: 415 ms, Serialization: 78 ms, Deserialization: 173 ms
10000 items, Size: 23800000, Completed: 4692 ms, Serialization: 785 ms, Deserialization: 2182 ms
-------------------------------------
TestProtobufImplicitAnnotatedEntities
100 items, Size: 237700, Completed: 35 ms, Serialization: 7 ms, Deserialization: 17 ms
1000 items, Size: 2377000, Completed: 429 ms, Serialization: 78 ms, Deserialization: 201 ms
10000 items, Size: 23770000, Completed: 4799 ms, Serialization: 796 ms, Deserialization: 2207 ms
----------------------------------
TestProtobufFullyAnnotatedEntities
100 items, Size: 238900, Completed: 33 ms, Serialization: 8 ms, Deserialization: 14 ms
1000 items, Size: 2389000, Completed: 423 ms, Serialization: 79 ms, Deserialization: 166 ms
10000 items, Size: 23890000, Completed: 4734 ms, Serialization: 782 ms, Deserialization: 2243 ms
------------------
TestBinaryEntities
100 items, Size: 426909, Completed: 82 ms, Serialization: 30 ms, Deserialization: 36 ms
1000 items, Size: 4183509, Completed: 1537 ms, Serialization: 384 ms, Deserialization: 997 ms
10000 items, Size: 41749512, Completed: 89895 ms, Serialization: 4173 ms, Deserialization: 83900 ms

Advertisements

Run time initialisation of protobuf-net v2 without annotation

Posted on

I have been using protobuf-net for the project at office but it’s only implemented for a limited number of entities (or business objects). We’re now trying to see how to implement this to the whole entities that we’re sending back n forth between the UI and the back end.

As we’re currently using a code generation “tool” to produce the code, we’re thinking to incorporate the annotation/attribute into the generated class/property code, however due to some not really technical reason, this is a bit complicated to achieve.

I thought of checking the site again to get some idea and I came across to this statement “allow use without attributes if you wish” on the page.

Then I tried to dig out more on the SO and found some more good leads on this. Apparently it’s very much possible to perform “registration” during run time mode which allows us NOT to use any annotation on the entities.

So thanks to the pointers, there are 2 alternatives for this:
1. With very minimal annotation on the class level

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] // only required on the class level
class PersonEntity
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

2. Without any annotation (using RuntimeTypeModel)

static void InitializeProtobufRunTime()
{
    var assembly = Assembly.GetAssembly(typeof(PlainEntities.PersonEntity));
    var types = assembly.GetTypes();
    foreach (var t in types.Where(x => x.Namespace.Contains("PlainEntities")))
    {
        Console.WriteLine("Processing {0}", t.FullName);
        var meta = RuntimeTypeModel.Default.Add(t, false);
        var index = 1;

        // find any derived class for the entity
        foreach (var d in types.Where(x => x.IsSubclassOf(t)))
        {
            var i = index++;
            Console.WriteLine("\tSubtype: {0} - #{1}", d.Name, i);
            meta.AddSubType(i, d);
        }

        // then add the properties
        foreach (var p in t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).Where(x => x.GetSetMethod() != null))
        {
            var i = index++;
            Console.WriteLine("\tProperty: {0} - #{1}", p.Name, i);
            meta.AddField(i, p.Name);
        }
    }
}

And both the above works quite well without any performance differences.


------------------
TestBinaryEntities
------------------
Process: 100000 items, MemorySize: 7400705, Completed in: 3877 ms, Serialization took: 676 ms, Deserialization took: 2948 ms

----------------------------------
TestProtobufFullyAnnotatedEntities
----------------------------------
Process: 100000 items, MemorySize: 3983490, Completed in: 682 ms, Serialization took: 164 ms, Deserialization took: 253 ms

-------------------------------------
TestProtobufImplicitAnnotatedEntities
-------------------------------------
Process: 100000 items, MemorySize: 3983490, Completed in: 595 ms, Serialization took: 104 ms, Deserialization took: 210 ms

-------------------------------
TestProtobufRuntimeRegistration
-------------------------------
Processing ProtobufTestConsole.PlainEntities.BaseEntity
Subtype: PersonEntity - #1
Property: Id - #2
Property: Gender - #3
Processing ProtobufTestConsole.PlainEntities.PersonEntity
Property: FirstName - #1
Property: LastName - #2
Property: Age - #3
Process: 100000 items, MemorySize: 4083490, Completed in: 646 ms, Serialization took: 113 ms, Deserialization took: 232 ms

Looking forward to get this in :)

Also attached the sample project for reference

Sample Project