Performance of calling delegates vs methods

61,102

Solution 1

I haven't seen that effect - I've certainly never encountered it being a bottleneck.

Here's a very rough-and-ready benchmark which shows (on my box anyway) delegates actually being faster than interfaces:

using System;
using System.Diagnostics;

interface IFoo
{
    int Foo(int x);
}

class Program : IFoo
{
    const int Iterations = 1000000000;

    public int Foo(int x)
    {
        return x * 3;
    }

    static void Main(string[] args)
    {
        int x = 3;
        IFoo ifoo = new Program();
        Func<int, int> del = ifoo.Foo;
        // Make sure everything's JITted:
        ifoo.Foo(3);
        del(3);

        Stopwatch sw = Stopwatch.StartNew();        
        for (int i = 0; i < Iterations; i++)
        {
            x = ifoo.Foo(x);
        }
        sw.Stop();
        Console.WriteLine("Interface: {0}", sw.ElapsedMilliseconds);

        x = 3;
        sw = Stopwatch.StartNew();        
        for (int i = 0; i < Iterations; i++)
        {
            x = del(x);
        }
        sw.Stop();
        Console.WriteLine("Delegate: {0}", sw.ElapsedMilliseconds);
    }
}

Results (.NET 3.5; .NET 4.0b2 is about the same):

Interface: 5068
Delegate: 4404

Now I don't have particular faith that that means delegates are really faster than interfaces... but it makes me fairly convinced that they're not an order of magnitude slower. Additionally, this is doing almost nothing within the delegate/interface method. Obviously the invocation cost is going to make less and less difference as you do more and more work per call.

One thing to be careful of is that you're not creating a new delegate several times where you'd only use a single interface instance. This could cause an issue as it would provoke garbage collection etc. If you're using an instance method as a delegate within a loop, you will find it more efficient to declare the delegate variable outside the loop, create a single delegate instance and reuse it. For example:

Func<int, int> del = myInstance.MyMethod;
for (int i = 0; i < 100000; i++)
{
    MethodTakingFunc(del);
}

is more efficient than:

for (int i = 0; i < 100000; i++)
{
    MethodTakingFunc(myInstance.MyMethod);
}

Could this have been the problem you were seeing?

Solution 2

I find it completely implausible that a delegate is substantially faster or slower than a virtual method. If anything the delegate should be negligibly faster. At a lower level, delegates are usually implemented something like (using C-style notation, but please forgive any minor syntax errors as this is just an illustration):

struct Delegate {
    void* contextPointer;   // What class instance does this reference?
    void* functionPointer;  // What method does this reference?
}

Calling a delegate works something like:

struct Delegate myDelegate = somethingThatReturnsDelegate();
// Call the delegate in de-sugared C-style notation.
ReturnType returnValue = 
    (*((FunctionType) *myDelegate.functionPointer))(myDelegate.contextPointer);

A class, translated to C, would be something like:

struct SomeClass {
    void** vtable;        // Array of pointers to functions.
    SomeType someMember;  // Member variables.
}

To call a vritual function, you would do the following:

struct SomeClass *myClass = someFunctionThatReturnsMyClassPointer();
// Call the virtual function residing in the second slot of the vtable.
void* funcPtr = (myClass -> vtbl)[1];
ReturnType returnValue = (*((FunctionType) funcPtr))(myClass);

They're basically the same, except that when using virtual functions you go through an extra layer of indirection to get the function pointer. However, this extra indirection layer is often free because modern CPU branch predictors will guess the address of the function pointer and speculatively execute its target in parallel with looking up the address of the function. I've found (albeit in D, not C#) that virtual function calls in a tight loop are not any slower than non-inlined direct calls, provided that for any given run of the loop they're always resolving to the same real function.

Solution 3

Since CLR v 2, the cost of delegate invocation is very close to that of virtual method invocation, which is used for interface methods.

See Joel Pobar's blog.

Solution 4

I did some tests (in .Net 3.5... later I will check at home using .Net 4). The fact is: Getting an object as an interface and then executing the method is faster than getting a delegate from a method then calling the delegate.

Considering the variable is already in the right type (interface or delegate) and simple invoking it makes the delegate win.

For some reason, getting a delegate over an interface method (maybe over any virtual method) is MUCH slower.

And, considering there are cases when we simple can't pre-store the delegate (like in Dispatches, for example), that may justify why interfaces are faster.

Here are the results:

To get real results, compile this in Release mode and run it outside Visual Studio.

Checking direct calls twice
00:00:00.5834988
00:00:00.5997071

Checking interface calls, getting the interface at every call
00:00:05.8998212

Checking interface calls, getting the interface once
00:00:05.3163224

Checking Action (delegate) calls, getting the action at every call
00:00:17.1807980

Checking Action (delegate) calls, getting the Action once
00:00:05.3163224

Checking Action (delegate) over an interface method, getting both at every call
00:03:50.7326056

Checking Action (delegate) over an interface method, getting the interface once, the delegate at every call
00:03:48.9141438

Checking Action (delegate) over an interface method, getting both once
00:00:04.0036530

As you can see, the direct calls are really fast. Storing the interface or delegate before, and then only calling it is really fast. But having to get a delegate is slower than having to get an interface. Having to get a delegate over an interface method (or virtual method, not sure) is really slow (compare the 5 seconds of getting an object as an interface to the almost 4 minutes of doing the same to get the action).

The code that generated those results is here:

using System;

namespace ActionVersusInterface
{
    public interface IRunnable
    {
        void Run();
    }
    public sealed class Runnable:
        IRunnable
    {
        public void Run()
        {
        }
    }

    class Program
    {
        private const int COUNT = 1700000000;
        static void Main(string[] args)
        {
            var r = new Runnable();

            Console.WriteLine("To get real results, compile this in Release mode and");
            Console.WriteLine("run it outside Visual Studio.");

            Console.WriteLine();
            Console.WriteLine("Checking direct calls twice");
            {
                DateTime begin = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                {
                    r.Run();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }
            {
                DateTime begin = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                {
                    r.Run();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking interface calls, getting the interface at every call");
            {
                DateTime begin = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                {
                    IRunnable interf = r;
                    interf.Run();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking interface calls, getting the interface once");
            {
                DateTime begin = DateTime.Now;
                IRunnable interf = r;
                for (int i = 0; i < COUNT; i++)
                {
                    interf.Run();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking Action (delegate) calls, getting the action at every call");
            {
                DateTime begin = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                {
                    Action a = r.Run;
                    a();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking Action (delegate) calls, getting the Action once");
            {
                DateTime begin = DateTime.Now;
                Action a = r.Run;
                for (int i = 0; i < COUNT; i++)
                {
                    a();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }


            Console.WriteLine();
            Console.WriteLine("Checking Action (delegate) over an interface method, getting both at every call");
            {
                DateTime begin = DateTime.Now;
                for (int i = 0; i < COUNT; i++)
                {
                    IRunnable interf = r;
                    Action a = interf.Run;
                    a();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking Action (delegate) over an interface method, getting the interface once, the delegate at every call");
            {
                DateTime begin = DateTime.Now;
                IRunnable interf = r;
                for (int i = 0; i < COUNT; i++)
                {
                    Action a = interf.Run;
                    a();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }

            Console.WriteLine();
            Console.WriteLine("Checking Action (delegate) over an interface method, getting both once");
            {
                DateTime begin = DateTime.Now;
                IRunnable interf = r;
                Action a = interf.Run;
                for (int i = 0; i < COUNT; i++)
                {
                    a();
                }
                DateTime end = DateTime.Now;
                Console.WriteLine(end - begin);
            }
            Console.ReadLine();
        }
    }

}
Share:
61,102
Paolo
Author by

Paolo

Updated on July 06, 2022

Comments

  • Paolo
    Paolo almost 2 years

    Following this question - Pass Method as Parameter using C# and some of my personal experience I'd like to know a little more about the performance of calling a delegate vs just calling a method in C#.

    Although delegates are extremely convenient, I had an app that did lots of callbacks via delegates and when we rewrote this to use callback interfaces we got an order of magnitude speed improvement. This was with .NET 2.0 so I'm not sure how things have changed with 3 and 4.

    How are calls to delegates handled internally in the compiler/CLR and how does this affect performance of method calls?


    EDIT - To clarify what I mean by delegates vs callback interfaces.

    For asynchronous calls my class could provide an OnComplete event and associated delegate which the caller could subscribe to.

    Alternatively I could create an ICallback interface with an OnComplete method that the caller implements and then registers itself with the class that will then call that method on completion (i.e. the way Java handles these things).

  • Jan
    Jan over 14 years
    Can you elaborate on what the compiler does in the last case? Does it create a new delegate instance on every iteration or?
  • MordechayS
    MordechayS over 14 years
    Would this change if you turned it into an event using the delegate?
  • Jon Skeet
    Jon Skeet over 14 years
    @JanJ: Check the compiled code with ildasm, but I believe it will, yes. @Chris S: Could you give more details about what you mean?
  • Paolo
    Paolo over 14 years
    That was always my assumption until I came across the anomaly I described in the question. Maybe as Jon suggests something else was the problem and I've got stuck on a "delegates are slower" meme by mistake.
  • Paolo
    Paolo over 14 years
    Thanks Jon, I don't think it was an excess of objects/garbage collection but your benchmark neatly shows that delegates are at least as fast so whatever the original cause I'll patch my internal knowledge with these results ;)
  • TamusJRoyce
    TamusJRoyce about 12 years
    You probably shouldn't include getting the delegate in the time it takes to run it.
  • supercat
    supercat over 11 years
    Conceptually, there's no reason why support for multicast delegates would have to slow invocation in the single-target case. If delegates with multiple targets set their internal method pointer to a special ExecuteMultiDelegate method, and its internal target reference to an array of structs holding (Object,Method) pairs, delegates could dispatch unconditionally to their method without checking whether there were multiple targets. The ExecuteMultiDelegate method would have to have some of the normal type-check behavior disabled, but that should be doable.
  • supercat
    supercat over 11 years
    Note that the approach I just described isn't AFAIK how MulticastDelegates are actually implemented, but it would be a mechanism for optimizing the most common (exactly one target) case.
  • Kiquenet
    Kiquenet almost 10 years
    Whats about performance ? Action/Func are implemented as delegates. Delegates are implemented in IL as compiler-generated classes with an Invoke() method. Calling foo() when foo is a delegate actually compiles down to calling foo.Invoke(), which in turn calls the destination code. If foo is an actual method instead of a delegate, calling foo() calls directly to the destination code with no Invoke() intermediate. See ILDASM for proof. stackoverflow.com/a/8449833/206730
  • Jon Skeet
    Jon Skeet almost 10 years
    @Kiquenet: If you're using an interface or a virtual method as well, that introduces an extra level of indirection as well. Yes, you can get slightly better performance if you just invoke a non-virtual method directly, but it's rarely actually significant in my experience.
  • RGS
    RGS almost 7 years
    @JonSkeet, Can you tell me real time scenario in which you will use delegate method instead of normal method call?
  • Jon Skeet
    Jon Skeet almost 7 years
    @RGS: Do you mean "real world" or genuinely "real time"?
  • RGS
    RGS almost 7 years
    @JonSkeet, When delegate is preferred to use? I have read lot of things. Still I am bit confused about delegate. Please tell me the real time scenario.
  • Jon Skeet
    Jon Skeet almost 7 years
    @RGS: Again, it's not clear whether you're using the term "real time" in the real-time computing sense, or whether you just mean "in real code". For the latter, have you ever written an event handler, e.g. button.Click += HandleButtonClick? Have you ever used LINQ? Stack Overflow comments really aren't appropriate for this sort of discussion though...
  • Engineer
    Engineer about 6 years
    If only there were more truly technical answers like this on SO, showing how the underlying implementations were achieved, instead of expecting askers to rely on blind faith that "it is so".
  • yoyo
    yoyo about 5 years
    Nice benchmarks, thank you. I tried a number of variations and determined that: direct calls are always the fastest; static direct calls are no faster than instance member direct calls; .NET 4 is slower for direct calls, though faster in some other cases; compiling with "/optimize+" helps, but "/debug-" and "/checked-" don't make any difference; "/platform:x64" doesn't affect timings, but "/platform:x86" does (faster in a couple cases, slower in most); separating tests into their own methods makes no difference; putting Runnable in a separate assembly makes no difference.
  • Matheus Rocha
    Matheus Rocha about 4 years
    Wouldn't the Action class add some overhead?
  • monkey0506
    monkey0506 over 2 years
    Obviously this code sample is quite old by now, but running this exact snippet across various online compilers (.NET Framework 4.7 through .NET 5) and my personal machine (.NET 6 RC), the interface version is anywhere from 0.33x to 5x faster. Changing the delegate to reference Program.Foo directly rather than going through the IFoo interface did show slight speed improvement, but the interface version still had a slight advantage in my recent tests.
  • Timo
    Timo over 2 years
    @monkey For clarification, by "0.33x to 5x faster" did you mean to say that the interface version takes only 33% to 50% of the duration required by the delegate version?
  • monkey0506
    monkey0506 about 2 years
    @Timo in my tests the interface version of the code ran in the range of 20% to 66.67% of the time that the version using delegates took to run. That is if a specific example using delegates ran in an average time of 2.30 seconds, the same example using interfaces ran on my machine in around 0.46 to 1.53 seconds. The performance gain varied slightly depending on the actual test code, but interface performed better in all of my tests.