New Column – different methods of dynamic dispatch.


My most recent column went live on MSDN today. It discusses different methods of dynamic execution of code.


Executive summary: Avoid Type.InvokeMember() if you can.


[Update: One of my readers sent me an article with some additional timings]

Comments (20)

  1. damien morton says:

    I spent some time doing exactly this kind of benchmarking last night. I was trying to construct a Weak Delegate class; a delegate that was a weak reference to its target, rather than a strong one. The idea being, that if the target gets GC’ed, the delegate becomes a no-op.

    The best I could come up with was a class derived from WeakReference that took a Delegate as a constructor param and deconstructed it into Method and Target fields. Actual invokation would be done using MethodInfo.Invoke.

    The result? a slim and elegant class that performs like crap. MethodInfo.Invoke is over several orders of magnitude slower that an interface or delegate call.

    Heres to hoping that Whidbey either has a weak delegate type, or at least has decent tools for building one.

    public class WeakDelegate : WeakReference

    {

    MethodInfo _method;

    public WeakDelegate(Delegate d) : base(d.Target)

    {

    this._method = d.Method;

    }

    public WeakDelegate(MethodInfo method, object target) : base(target)

    {

    this._method = method;

    }

    public MethodInfo Method { get { return this._method; } }

    public object Invoke(object[] args)

    {

    if (this.IsAlive)

    return this._method.Invoke(this.Target, args);

    GC.KeepAlive(this.Target);

    return null;

    }

    }

  2. Dilton McGowan II says:

    Good article, if the end performance isn’t as impressive as calling direct (and I understand it can never be) then why not make the syntax more readable and maintainable? Something like JavaScript’s Eval() method?

  3. Ferris Beuller says:

    GENIUS! Put things into a language and then say "AVOID using it" INGENIUS!.

    Why not just fix the actual problem and not passing the buck.

  4. You missed abstract class case from the test. According to what I’ve read, interface should be using 3 indirections thru vtbl, while abstract class only 2. However I didn’t noticed this in slightly updated Race program – they finish almost synchronously. Also, you may want to make a note in the article about NOT running the test app in VS.NET even when release-built, due to different CLR behaviour in debugger environment.

  5. Specifically about the InvokeMember()

    You mentioned in your article that it does a lot of work to make sure the call is correct.

    You also mentioned the boxing of parameters and return type plays a role.

    I don’t understand, why the difference in speed is so great. It sounds like there might be a lot of superfluous checking going on.

    This whole area is important to me and many others, will we see any work done in this arena?

  6. BigJimInDC says:

    From where I’m sitting, those benchmarks indicate the overhead associated with using those different calling methods. So, if you are sitting in a loop calling a method that doesn’t do a whole lot (such as the one in the example above), then the overhead is extreme. At the same time, if you are making non-looped single method calls to unknown external API’s, then the overhead might be acceptable compared to the functionality gained.

    Hell, even in your tests, InvokeMember() was able to be called 730,000 times in a second, which should be significantly more than adequate if the API being called is properly designed. Hopefully when needing to use an external API in this manner, the API would support more robust methods for performing operations, as opposed to forcing the user to need to call ridiculously granular methods to implement the functionality desired.

    The only interesting thing to me is the significant loss of performance when using an interface to make those calls. It might be the fastest non-direct way to make those calls, but a 90% loss of performance is bordering on unacceptable.

  7. Dmitriy Zaslavskiy says:

    Eric,

    First of all CLR inline your method call. Therefore you are seeing this huge diff. in speed. you should apply [MethodImpl(MethodImplOptions.NoInlining)] to Process method.

  8. Dmitriy Zaslavskiy says:

    Sorry, Pressed enter at wrong time.

    Second, you are not likely to see a difference between abstract class and interface in this test, because once again CLR will be able to hoist "interface finding".

    Eric,

    Once we are on subject of delegates, and you are the PM, could you allow the following:

    class Foo

    {

    public string Prop { set {} }

    }

    delegate void SetterDelegate(string s);

    Foo f = new Foo();

    SetterDelegate setter = new SetterDelegate(f.Prop)

    And also even more important

    Foo x = new Foo();

    setter.Target = x;

  9. Dmitry, are test results also influenced by csc always emitting callvirt?

  10. Frans Bouma says:

    I’m shocked by the huge difference between interface calling and direct calling. Is that the reason why .NET doesn’t use interfaces internally but just base classes and MS’ guidelines recommend base class usage instead of interfaces?

    What I wonder also is that the slowness of interface calls can be greatly reduced when the JIT does dynamic inlining. After all: the first time the call to the interface member, the method is known, the second time, it doesn’t have to look it up, it’s the same code so it can re-use the same information where the method is located and inline the method in the loop, eventually do loop unwinding as an ultimate optimization. Will the .NET JIT eventually do dynamic inlining or not? (as far as I know, the Java jit does this)

  11. Dmitriy Zaslavskiy says:

    Ilya,

    Test result wihout [MethodImpl(MethodImplOptions.NoInlining)] didn’t get effected by callvirt since the method was inlined. With the attribute, callvirt on non virt method results in just 1 extra instruction:

    cmp dword ptr [ecx],ecx

    which is not terrible but does have a cost.

    Frans,

    The difference is in fact very small if your method in not inlined. At least as of now CLR doesn’t inline virtual functions (I haven’t checked whether it does it for sealed classes)

  12. Fabrice says:

    With Pierrick Gourlain, we prepared an updated source code which involves more cases.

    Check it out: http://weblogs.asp.net/fmarguerie/archive/2004/03/05/84706.aspx