Performance of Calling Delegates VS Methods

Performance of calling delegates vs methods

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?

Performance of reflection method call vs delegate call

You're just testing 100 iterations. You're mostly testing one-time startup overhead. Increase the iteration count until each test takes 1 second. That way the overhead disappears in the noise.

Currently, your code runs for .5 milliseconds. That is far in the noise range. After fixing that I get:

00:00:00.9711365
00:00:01.0958751 //Slightly slower

This benchmark uses 1e7 iterations whereas the previous one used 1e2.
Also make sure to test in Release mode without debugger attached on the bitness that you care about.

Why are Func delegates so much slower

You're creating a new delegate object on every call. It's not surprising that that has a fair amount of overhead.

If you either use a lambda expression that doesn't capture this or any local variables (in which case the compiler can cache it in a static field) or if you explicitly create a single instance and store that in a field yourself, most of the overhead goes away.

Here's a modified version of your test:

public class Calculations
{
public Random RandomGeneration = new Random();
private Func<int> exprField;

public Calculations()
{
exprField = () => RandomGeneration.Next() * RandomGeneration.Next();
}

[Benchmark]
public void CalculateNormal()
{
var s = RandomGeneration.Next() * RandomGeneration.Next();
}

[Benchmark]
public void CalculateUsingFunc()
{
Calculate(() => RandomGeneration.Next() * RandomGeneration.Next());
}

[Benchmark]
public void CalculateUsingFuncField()
{
Calculate(exprField);
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Calculate(Func<int> expr)
{
return expr();
}
}

And the results on my machine:

|                  Method |     Mean |    Error |   StdDev |
|------------------------ |---------:|---------:|---------:|
| CalculateNormal | 27.61 ns | 0.438 ns | 0.388 ns |
| CalculateUsingFunc | 48.74 ns | 1.009 ns | 0.894 ns |
| CalculateUsingFuncField | 32.53 ns | 0.698 ns | 0.717 ns |

So there's still a bit of overhead, but much, much less than before.

Why one delegate is faster than the other?

Using a decompiler, we can discover the following:

Implementation of GetAction1<T>():

IL_0000: ldnull
IL_0001: ldftn void ConsoleApp1.UnderTest::ActionInt(int32)
IL_0007: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_000c: castclass class [System.Runtime]System.Action`1<!!0/*T*/>
IL_0011: ldftn instance void class [System.Runtime]System.Action`1<!!0/*T*/>::Invoke(!0/*T*/)
IL_0017: newobj instance void class ConsoleApp1.UnderTest/MyAction`1<!!0/*T*/>::.ctor(object, native int)
IL_001c: ret

Implementation of GetAction2<T>():

IL_0000: ldnull
IL_0001: ldftn void ConsoleApp1.UnderTest::ActionInt(int32)
IL_0007: newobj instance void class ConsoleApp1.UnderTest/MyAction`1<int32>::.ctor(object, native int)
IL_000c: castclass class ConsoleApp1.UnderTest/MyAction`1<!!0/*T*/>
IL_0011: ret

You can see in the first case that it is actually creating two delegates, and chaining one to the other.

In the second case it is only creating one delegate.

I can't explain the exact reason for this, but I would think that it's because of the extra cast to object in GetAction1.


There appears to be an even faster implementation, namely:

 public static MyAction<T> GetAction3<T>()
=> x => ActionInt((int)(object)x);

This generates much longer IL code:

IL_0000: ldsfld       class ConsoleApp1.UnderTest/MyAction`1<!0/*T*/> class ConsoleApp1.UnderTest/'<>c__4`1'<!!0/*T*/>::'<>9__4_0'
IL_0005: dup
IL_0006: brtrue.s IL_0021
IL_0008: pop
IL_0009: ldsfld class ConsoleApp1.UnderTest/'<>c__4`1'<!0/*T*/> class ConsoleApp1.UnderTest/'<>c__4`1'<!!0/*T*/>::'<>9'
IL_000e: ldftn instance void class ConsoleApp1.UnderTest/'<>c__4`1'<!!0/*T*/>::'<GetAction3>b__4_0'(!0/*T*/)
IL_0014: newobj instance void class ConsoleApp1.UnderTest/MyAction`1<!!0/*T*/>::.ctor(object, native int)
IL_0019: dup
IL_001a: stsfld class ConsoleApp1.UnderTest/MyAction`1<!0/*T*/> class ConsoleApp1.UnderTest/'<>c__4`1'<!!0/*T*/>::'<>9__4_0'
IL_001f: stloc.0 // V_0
IL_0020: ldloc.0 // V_0
IL_0021: ret

And yet it is faster for both the call to GetAction3() and executing the action that it returns.

Here's the benchmark program I tested with:

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace ConsoleApp1;

public static class Program
{
public static void Main()
{
var summary = BenchmarkRunner.Run<UnderTest>();
}
}

public class UnderTest
{
public delegate void MyAction<T>(T val);
public static int counter;

public static MyAction<T> GetAction1<T>()
=> new MyAction<T>((Action<T>)(object)ActionInt);

// Enigmativity's suggestion
public static MyAction<T> GetAction2<T>()
=> (MyAction<T>)(Delegate)(MyAction<int>)ActionInt;

public static MyAction<T> GetAction3<T>()
=> x => ActionInt((int)(object)x);

public static MyAction<int> Act1 = GetAction1<int>();
public static MyAction<int> Act2 = GetAction2<int>();
public static MyAction<int> Act3 = GetAction3<int>();

static void ActionInt(int val) { counter++; }

[Benchmark]
public void Action1()
{
_ = GetAction1<int>();
}

[Benchmark]
public void Action2()
{
_ = GetAction2<int>();
}

[Benchmark]
public void Action3()
{
_ = GetAction3<int>();
}

[Benchmark]
public void RunAction1()
{
Act1(0);
}

[Benchmark]
public void RunAction2()
{
Act2(0);
}

[Benchmark]
public void RunAction3()
{
Act3(0);
}
}

And the results:

|     Method |       Mean |     Error |    StdDev |
|----------- |-----------:|----------:|----------:|
| Action1 | 13.3355 ns | 0.1670 ns | 0.1480 ns |
| Action2 | 6.9685 ns | 0.1313 ns | 0.1228 ns |
| Action3 | 1.3437 ns | 0.0321 ns | 0.0285 ns |
| RunAction1 | 2.4100 ns | 0.0454 ns | 0.0425 ns |
| RunAction2 | 1.6493 ns | 0.0594 ns | 0.0527 ns |
| RunAction3 | 0.8347 ns | 0.0295 ns | 0.0276 ns |

Of course, none of the actions actually use the int passed to them, since they all just call ActionInt() which ignores its argument.

I suppose you could also implement it as:

public static MyAction<T> GetAction3<T>()
=> _ => ActionInt(0);

which might be even faster, but I haven't tried that.

When & why to use delegates?

I agree with everything that is said already, just trying to put some other words on it.

A delegate can be seen as a placeholder for a/some method(s).

By defining a delegate, you are saying to the user of your class, "Please feel free to assign any method that matches this signature to the delegate and it will be called each time my delegate is called".

Typical use is of course events. All the OnEventX delegate to the methods the user defines.

Delegates are useful to offer to the user of your objects some ability to customize their behavior.
Most of the time, you can use other ways to achieve the same purpose and I do not believe you can ever be forced to create delegates. It is just the easiest way in some situations to get the thing done.

what role event delegates play?

This can help you to understand events and delegates:

Events in .NET are based on the delegate model. The delegate model follows the observer design pattern, which enables a subscriber to register with and receive notifications from a provider. An event sender pushes a notification that an event has happened, and an event receiver receives that notification and defines a response to it. This article describes the major components of the delegate model, how to consume events in applications, and how to implement events in your code.

An event is a message sent by an object to signal the occurrence of an action. The action can be caused by user interaction, such as a button click, or it can result from some other program logic, such as changing a property’s value. The object that raises the event is called the event sender. The event sender doesn't know which object or method will receive (handle) the events it raises. The event is typically a member of the event sender; for example, the Click event is a member of the Button class, and the PropertyChanged event is a member of the class that implements the INotifyPropertyChanged interface.

To define an event, you use the C# event or the Visual Basic Event keyword in the signature of your event class, and specify the type of delegate for the event. Delegates are described in the next section.

Typically, to raise an event, you add a method that is marked as protected and virtual (in C#) or Protected and Overridable (in Visual Basic). Name this method OnEventName; for example, OnDataReceived. The method should take one parameter that specifies an event data object, which is an object of type EventArgs or a derived type. You provide this method to enable derived classes to override the logic for raising the event. A derived class should always call the OnEventName method of the base class to ensure that registered delegates receive the event.

A simple example is the Button control: when you click on it, the event OnClick is raised and the control call all delegates that have suscribed to it using:

Click += SomeMethod;

Or (it is the same):

Click += new EventHandler(SomeMethod);

If fact, it adds a reference to the method in the event handler list.

An event is just a list of methods to call in case of the event is raised, like with the button click.

A delegate is just a signature of a method without a body implementation, also called a prototype, to be used as a type, to have strong typing when adding events to an event handler like the button click. So when you assign a method implemented, by its name, it must match the delegate signature (return type and parameters, the name of the delegate itself is not pertinent except for humans).

We also can use lambda:

myButton1.Click += (sender, e) => Close();

myButton2.Click += (sender, e) => { MessageBox.Show("Closing"); Close() };

Handling and raising events (MS Docs)

C# - Events(TutorialsPoint)

C# - Events (TutorialsTeacher)

What are C# Events? (YouTube)

C# Tutorial: Events/Event Handlers (YouTube)

Why is Action/Func better than a regular Method in .Net?

There are two sides to your question: style and performance.

For code style, using delegates makes things a little messy. There is no function name associated with the body of code (anonymous method), argument types are inferred, and when overused this can lead to large function blocks containing lots of smaller independent chunks of code in delegates. It's not pretty to look at, it can be hard to figure out what the code is actually doing, and it's hard to find what you're looking for.

For performance, delegates introduce an indirection that is not as fast to execute as a plain old method. The difference is small, but when used to excess it could become noticeable.

Any tool has appropriate and excessive uses. It's appropriate to use Action or Func for a callback parameter when you want to pass a callback routine into a function. Using Action or Func as a replacement for declaring functions in general is a misuse, IMO.



Related Topics



Leave a reply



Submit