Can Delegate.DynamicInvoke be avoided in this generic code?
Asked Answered
M

3

11

This question is partly about delegates, and partly about generics.

Given the simplified code:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Delegate> _actionByType 
        = new Dictionary<Type, Delegate>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = action;
    }

    public void ProcessItem(object item)
    {
        Delegate action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            // Can this call to DynamicInvoke be avoided?
            action.DynamicInvoke(item);
        }
    }
}

I read elsewhere on SO that invoking a delegate directly (with parenthesis) is orders of magnitude faster than calling DynamicInvoke, which makes sense.

For the code sample above, I'm wondering whether I can perform the type checking and somehow improve performance.

Some context: I have a stream of objects that get farmed out to various handlers, and those handlers can be registered/unregistered at runtime. The above pattern functions perfectly for my purposes, but I'd like to make it snappier if possible.

One option would be to store Action<object> in the Dictionary, and wrap the Action<T> delegates with another delegate. I haven't yet compared the performance change that this second indirect call would affect.

Making answered 12/7, 2009 at 13:50 Comment(0)
T
26

I strongly suspect that wrapping the calls would be a lot more efficient than using DynamicInvoke. Your code would then be:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Action<object>> _actionByType 
        = new Dictionary<Type, Action<object>>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = item => action((T) item);
    }

    public void ProcessItem(object item)
    {
        Action<object> action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            action(item);
        }
    }
}

It's worth benchmarking it, but I think you'll find this a lot more efficient. DynamicInvoke has to check all the arguments with reflection etc, instead of the simple cast in the wrapped delegate.

Tjaden answered 12/7, 2009 at 13:54 Comment(5)
This can however result in a strange, confusing stacktrace.Sew
I doubt that it would be significantly more confusing than the one generated by a call to DynamicInvoke.Tjaden
@Jon, thanks for adding some weight to my suspicion. I'll try profiling this tomorrow when in the office.Making
Nice solution, tested it, performance is approx. 10 times betterGelasias
@ValBakhtin, I measured it as roughly 270 times faster. That was a few years ago though -- the CLR may have changed.Making
M
8

So I did some measurements on this.

var delegates = new List<Delegate>();
var actions = new List<Action<object>>();

const int dataCount = 100;
const int loopCount = 10000;

for (int i = 0; i < dataCount; i++)
{
    Action<int> a = d => { };
    delegates.Add(a);
    actions.Add(o => a((int)o));
}

var sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var action in actions)
        action(i);
}
Console.Out.WriteLine("{0:#,##0} Action<object> calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var del in delegates)
        del.DynamicInvoke(i);
}
Console.Out.WriteLine("{0:#,##0} DynamicInvoke calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

I created a number of items to indirectly invoke to avoid any kind of optimisation the JIT might perform.

The results are quite compelling!

1,000,000 Action calls in 47.172 ms
1,000,000 Delegate.DynamicInvoke calls in 12,035.943 ms

1,000,000 Action calls in 44.686 ms
1,000,000 Delegate.DynamicInvoke calls in 12,318.846 ms

So, in this case, substituting the call to DynamicInvoke for an extra indirect call and a cast was approximately 270 times faster. All in a day's work.

Making answered 13/7, 2009 at 10:57 Comment(1)
Running these measurements with latest .NET 8 results in DynamicInvoke taking only twice as long as Action<object>. So, it seems .NET did some serious progress here.Pledget
S
1

If you need to extend this to wrapping member invocations from classes without using Reflection.Emit you can do so by creating a series of compiler hints that can map a class and a function parameter list or return type.

Basically you need to create lambdas that take objects as parameters and return an object. Then use a generic function that the compiler sees AOT to create a cache of suitable methods to call the member and cast the parameters. The trick is to create open delegates and pass them through a second lamda to get to the underlying hint at runtime.

You do have to provide the hints for every class and signature (but not every method or property).

I've worked up a class here that does this, it's a bit too long to list out in this post.

In performance testing it's no where near as good as the example above, but it is generic which means it works in the circumstances I needed. Performance around 4.5x on reading a property compared to Invoke.

Spit answered 18/4, 2012 at 21:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.