dimanche 17 février 2019

C#/.NET Most performant way to call a method dynamically

We are developing a system, which reads commands from a tcp/ip stream and then executes those commands. Commands consist of a method call on an object also identified by an id int the command. You could think of a command as the information of an element id (addressing element we want to call a the command on) and an command id (addressing the method which should be called on the element). Additionally we also have the issue that we need to check some kind of permissions on every command and also how this command should be executed. (Should it be started in a new Thread, etc.)

An example of how such a command call could look like would be this:

class Callee
{
    public void RegularCall(int command, parameters)
    {
        switch (command)
        {
            case 1: // Comand #1
                // Check if the permissions allow this command to be called.
                // Check if it should be outsourced to the ThreadPool and
                // call it accordingly. +Other Checks.
                // Finally execute command #1.
                break;
            case 2: // Comand #2
                // Check if the permissions allow that command to be called.
                // Check if it should be outsourced to the ThreadPool and
                // call it accordingly. +Other Checks.
                // Finally execute command #2.
                break;
            // Many more cases with various combinations of permissions and
            // Other flags.
        }
    }
}

And somewhere:

static Dictionary<int, Callee> callees = new Dictionary<int, Callee>();

static void CallMethod(int elementId, int commandId, parameters)
{
    callees[elementId].RegularCall(commandId, parameters);
}

However, this approach is some kind of unelegant:

  • This may be error prone due to copying the same code over and over again.
  • In some circumstances it's hard to see, which commands exist and what their flags are.
  • The command method is full of checks which could have made outside the method.

My first approach was by using reflection, which would have looked that way:

class Callee
{
    [Command(1)]
    [Permissions(0b00111000)]
    [UseThreadPool]
    public void SpeakingNameForCommand1(parameters)
    {
        // Code for command #1.
    }

    [Command(2)]
    [Permissions(0b00101011)]
    public void SpeakingNameForCommand2(parameters)
    {
        // Code for command #2.
    }

    // Again, many more commands.
}

This code must have been initialized with some reflection heavy code:

  1. Find all classes which may represent an element.
  2. Find all methods which have a command attribute, etc.
  3. Store all those information in a dictionary, including the corresponding MethodInfo.

A call of a received command would look like this, where CommandInfo is a class containing all the information required for the call (MethodInfo, run in ThreadPool, permissions...):

static Dictionary<int, CommandInfo> commands = new Dictionary<int, CommandInfo>();

static void CallMethod(int elementId, int commandId)
{
    CommandInfo ci = commands[commandId];

    if (ci.Permissions != EVERYTHING_OK)
        throw ...;

    if (ci.UseThreadPool)
        ThreadPool.Queue...(delegate { ci.MethodInfover.Invoke(callees[elementId], params); });
    else
        ci.MethodInfo.Invoke(callees[elementId], params);
}

When I micro-benchmark this, the call to MethodInfo.Invoke is about 100x slower than the direct call. The question is: Is there a faster way of calling those "command" methods, without losing the elegance of the attributes defining the way how those commands should be called?

I also tried deriving a delegate from the MethodInfo. However, this didn't work well, because I need to be able to call the method on any instance of the Callee class and don't want to reserve the memory for the delegate for every possible element * commands. (There will be many elements.)

Just to make this clear: MethodInfo.Invoke is 100x slower than the function call including the switch/case statement. This excludes the time to walk over all classes, methods and attributes, because those informations have already been prepared.





Aucun commentaire:

Enregistrer un commentaire