notes from a passionate developer

Developer that lives by the mantra "code is meant to be shared".




This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

Routemeister optimizations

Daniel WertheimDaniel Wertheim

Just release v0.2.0 of Routemeister. In this release the internals has be rewritten to use IL Emits to get quicker method invocations. This also allows Routemeister move away from singletons when it comes to resolving instances of the classes implementing the message handlers. So now, you can have this controlled in your IoC-container.

If you don't know what Routemeister is, I have an introductory blog post about it that you can go and read.

Below are some numbers comparing Routemeister against pure C# method calls.

It's not really a fair comparison. Since the C# variants will not really route a message. It knows where to call each time. That's why I included the Routemeister manual Route stats.

===== Pure C# - Shared handler =====
1,25546666666667ms / 100000calls  
1,25546666666667E-05ms / call

===== Pure C# - New handler =====
1,45813333333333ms / 100000calls  
1,45813333333333E-05ms / call

===== Routemeister - Shared handler =====
12,1029333333333ms / 100000calls  
0,000121029333333333ms / call

===== Routemeister - New handler =====
12,1083333333333ms / 100000calls  
0,000121083333333333ms / call

===== Routemeister manual Route - Shared handler =====
1,73613333333333ms / 100000calls  
1,73613333333333E-05ms / call

===== Routemeister manual Route - New handler =====
1,67353333333333ms / 100000calls  
1,67353333333333E-05ms / call  


Instead of using some Delegate.CreateDelagate and converting typed Func<T, Task> to Func<object, Task> I went ahead and defined a custom delegate MessageHandlerInvoker and then used this with IL Emits instead.

internal delegate Task MessageHandlerInvoker(object messageHandlerContainer, object message);

internal static class IlMessageHandlerInvokerFactory  
    internal static MessageHandlerInvoker GetMethodInvoker(MethodInfo methodInfo)
        var dynamicMethod = new DynamicMethod(
            new[] { typeof(object), typeof(object) },
            methodInfo.DeclaringType?.Module ?? methodInfo.Module);
        var il = dynamicMethod.GetILGenerator();

        var paramTypes = methodInfo.GetParameters()
            .Select(p => p.ParameterType)
        var locals = new LocalBuilder[paramTypes.Length];
        locals[0] = il.DeclareLocal(paramTypes[0]);

        //Load Message argument
        //Cast object to Message-type
        il.Emit(OpCodes.Castclass, paramTypes[0]);
        //Load message into variable
        il.Emit(OpCodes.Stloc, locals[0]);

        //Load Message handler container
        //(the instance of the class holding the method)
        //Load variable with Message
        il.Emit(OpCodes.Ldloc, locals[0]);

        il.EmitCall(OpCodes.Call, methodInfo, null);

        return (MessageHandlerInvoker)dynamicMethod

Router tweaks

I also cut a few milliseconds by changing how routes were located inside MessageRoutes.

public MessageRoute GetRoute(Type messageType)  
    return _state.ContainsKey(messageType)
        ? _state[messageType]
        : null;

was switched to:

public MessageRoute GetRoute(Type messageType)  
    MessageRoute route;

    return _state.TryGetValue(messageType, out route)
        ? route
        : new MessageRoute(messageType);

Note also that I now return an "Empty" message route for the type. This allowed me to remove some logic for null checks in the SequentialAsyncMessageRouter.

That's it. Some IL Emits and some more effective routing, by tweaking how the routes were located. Enough for now.


Developer that lives by the mantra "code is meant to be shared".