83

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).

Community
  • 1
  • 1
Paolo
  • 22,188
  • 6
  • 42
  • 49
  • 1
    I'm not clear on what you're asking... callbacks interfaces _are_ delegates. – Joel Coehoorn Jan 17 '10 at 21:33
  • 1
    See http://stackoverflow.com/questions/1269452/is-using-delegates-excessively-a-bad-idea-for-performance and http://stackoverflow.com/questions/304770/does-using-delegates-slow-down-my-net-programs - possible duplicates? – itowlson Jan 17 '10 at 21:34
  • delegate are necessary if you are running separate threads and need to interface with the UI thread.. so you need to refine your question more to be more localized and less generic. –  Jan 17 '10 at 21:44

4 Answers4

91

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?

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Can you elaborate on what the compiler does in the last case? Does it create a new delegate instance on every iteration or? –  Jan 17 '10 at 22:44
  • 2
    Would this change if you turned it into an event using the delegate? – Chris S Jan 17 '10 at 22:55
  • @JanJ: Check the compiled code with ildasm, but I believe it will, yes. @Chris S: Could you give more details about what you mean? – Jon Skeet Jan 17 '10 at 23:17
  • 4
    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 ;) – Paolo Jan 18 '10 at 08:40
  • 2
    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. http://stackoverflow.com/a/8449833/206730 – Kiquenet Jul 08 '14 at 11:55
  • 3
    @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. – Jon Skeet Jul 08 '14 at 12:02
  • @JonSkeet, Can you tell me real time scenario in which you will use delegate method instead of normal method call? – RGS Jul 17 '17 at 18:07
  • 1
    @RGS: Do you mean "real world" or genuinely "real time"? – Jon Skeet Jul 17 '17 at 18:15
  • @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. – RGS Jul 17 '17 at 18:18
  • 1
    @RGS: Again, it's not clear whether you're using the term "real time" in the [real-time computing](https://en.wikipedia.org/wiki/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... – Jon Skeet Jul 17 '17 at 18:24
  • 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. – monkey0506 Oct 25 '21 at 00:15
  • @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? – Timo Jan 24 '22 at 10:08
  • @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 `delegate`s took to run. That is if a specific example using `delegate`s ran in an average time of 2.30 seconds, the same example using `interface`s 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. – monkey0506 Feb 24 '22 at 12:39
28

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.

dsimcha
  • 67,514
  • 53
  • 213
  • 334
  • 1
    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 Jan 18 '10 at 08:39
  • 8
    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". – Engineer Apr 17 '18 at 09:48
24

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.

Pete Montgomery
  • 4,060
  • 3
  • 30
  • 40
7

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();
        }
    }

}
nawfal
  • 70,104
  • 56
  • 326
  • 368
Paulo Zemek
  • 147
  • 2
  • 1
  • 3
    You probably shouldn't include getting the delegate in the time it takes to run it. – TamusJRoyce May 01 '12 at 18:55
  • 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. – yoyo Feb 15 '19 at 22:41
  • Wouldn't the `Action` class add some overhead? – Matheus Rocha Apr 22 '20 at 14:59