Delegates As Function Pointers: Performance Boost?
C# delegates can be used like function pointers. Assign it once and you don’t have to use an if
over and over. But is the overhead of the delegate worth it? Today’s article puts it to the test to see if this a valid performance boost versus just using an if
over and over. Read on to see if a delegate is worth your time.
Here’s a quick overview of the two approaches so you can see how to use delegates like function pointers:
// The 'if' approach foreach (var cur in list) { if (TestCondition) { // Do thing A... } else { // Do thing B... } } // The delegate approach using delegates as function pointers delegate void Del(); void DoThingA() { } void DoThingB() { } void Foo() { Del del; if (TestCondition) { del = DoThingA; } else { del = DoThingB; } foreach (var cur in list) { del(); // note: no 'if' } }
Now let’s try out these two methods a bunch of times to see which one ends up being faster. Here’s the script:
using System; using System.Diagnostics; using UnityEngine; public class TestScript : MonoBehaviour { private string report = string.Empty; private const int NumIterations = 10000000; private Action del; private int count; private bool choice; void Start() { choice = true; del = AddOne; var stopwatch = new Stopwatch(); stopwatch.Start(); for (var i = 0; i < NumIterations; ++i) { Choose(); } var ifTime = stopwatch.ElapsedMilliseconds; stopwatch.Reset(); stopwatch.Start(); for (var i = 0; i < NumIterations; ++i) { del(); } var delegateTime = stopwatch.ElapsedMilliseconds; report = "Method,Time\n" + "If," + ifTime + "\n" + "Delegate," + delegateTime; } void Choose() { if (choice) { count++; } else { count--; } } void AddOne() { count++; } void SubtractOne() { count--; } void OnGUI() { GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report); } }
If you want to try out the test yourself, simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.10.4
- Unity 5.1.2f1, Mac OS X Standalone, x86_64, non-development
- 640×480, Fastest, Windowed
And here are the results I got:
Method | Time |
---|---|
If | 28 |
Delegate | 48 |
Even after 10 million iterations the simple if
approach is almost twice as fast as using a delegate as a function pointer. The delegate overhead—even in the fastest case—is simply too great compared to the lowly if
. Instead, if you can, consider a third approach:
if (TestCondition) { foreach (var cur in list) { // Do thing A... } } else { foreach (var cur in list) { // Do thing B... } }
This approach only does the if
check once and doesn’t use delegates, so it should be the fastest.
Have you used delegates to gain performance? Found them losing you performance? Post about it in the comments!
#1 by Brad on August 10th, 2015 ·
As a Visual Studio console application (release build) with .NET 4.5.1, using delegates is consistently faster.
If: 126
Delegate: 47
Results are +/- 4 milliseconds.
#2 by jackson on August 10th, 2015 ·
Thanks again for chiming in with your test results. It seems like Microsoft .NET has received some nice optimizations that Unity’s Mono-based VM hasn’t.
#3 by Leucaruth on August 12th, 2015 ·
Maybe i’m just sleepy or I’m overthinking things, but I fail to see how can you substitute if’s with delegates in these examples.
In the first code block the foreach for the delegate will always execute A or B but not both.
In the second block you dont even take on account SubtractOne() so I suppose this one and previous blocks are just examples as you are most interested in the performance part.
But, how can you really exchange an If clause with a delegate method? Sorry if this is out of the scope of this article, but i’m just curious.
Thank you for all your good work :)
#4 by jackson on August 12th, 2015 ·
Your question is well within the scope of this article! :)
I suppose one bit of information I left out is that you need to make the same choice for each iteration of the loop. In the example app the choice is
if (choice) count++; else count--;
but sincechoice
is alwaystrue
this always executescount++;
which is the same asAddOne()
. In the case of the delegate, it’s always assigned toAddOne()
so that is always executed. The loops are therefore equivalent.Hopefully this helps clear things up, but please let me know if you still have any questions or anything else to add.