# C# delegates blow my mind

This topic is 3560 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I noticed that C# does not have an inline keyword for functions. Seeing this, I wanted to see how fast a function call was, and if it really made much of a difference putting the code inline VS putting it in a function call. I did a test executing the same code with 4 different approaches. The code executed in each approach is 9 operations (add and subtract). The 4 approaches are: 1 - putting the code all inline 2 - putting the code in a function and calling the function 3 - using a delegate to call the function 4 - putting each one of the 9 operations in their own function, and using a multicast delegate to call the 9 functions My goal was to see how many times the code could be executed in a specified length of time. I specify how long the approaches should run for and how many times they should be ran (the same values are used for all four approaches). I then take the average number of times the code was executed for each approach and compare them. I was expecting approach 1 to be the fastest, approaches 2 and 3 to be about the same, but much slower than approach 1, and approach 4 to be very slow. To my amazement appraoches 1, 2 and 3 all perform about the same, and approach 4 suffers maybe a 1% performance hit, if that. This was not the results that I was expecting. I have done the test many times, specifying different amounts of time the approaches should run for (1 - 60 seconds), and the number of times they should be ran (1 - 20, then take the average), and I get consistent results. Below is my code; I want to make sure that there is nothing I am overlooking. I use a high resolution Stopwatch to control how long each approach runs for, and I randomly pick the order that each of the 4 approaches is called in. Also, for the function calls I pass a class object as the single parameter, which is the object to update, and I make sure I'm not running any other applications when I do the test. If you can spot a potential problem in my code (or something that I am not considering) let me know. I just found this very interesting and thought I would share it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace Delegate_Speed_Test
{
public partial class Form1 : Form
{
// Class to hold data to update
class CParticle
{
public float f1;
public float f2;
public float f3;
public int i4;
public int i5;
public int i6;
public string s7;
public string s8;
public long l9;

public CParticle()
{
f1 = f2 = f3 = 0.0f;
i4 = i5 = i6 = 0;
s7 = s8 = "";
l9 = 0;
}
}

// Define the Delegate function structure
delegate void UpdateDelegate(CParticle cParticle);

// Variables used to calculate the average number of times executed
long mlNumberOfTimesRanInline = 0;
long mlNumberOfTimesRanFunction = 0;
long mlNumberOfTimesRanDelegate = 0;
long mlNumberOfTimesRanMulticastDelegate = 0;

Random mcRandom = new Random();

public Form1()
{
InitializeComponent();

}

// Show how long the test will take to run using the given duration and number of times to run
{
float fTimeInSeconds = (float)(numericNumberOfTimesToRun.Value * numericLengthOfTimeToRun.Value * 4);
float fTimeInMinutes = fTimeInSeconds / 60.0f;

labelTimeNeededToRunInSeconds.Text = fTimeInSeconds.ToString();
labelTimeNeededToRunInMinutes.Text = fTimeInMinutes.ToString();
}

// Start the test
private void buttonStart_Click(object sender, EventArgs e)
{
// Reset the number of times each has ran
mlNumberOfTimesRanInline = 0;
mlNumberOfTimesRanFunction = 0;
mlNumberOfTimesRanDelegate = 0;
mlNumberOfTimesRanMulticastDelegate = 0;

int iIndex = 0;
for (iIndex = 0; iIndex < numericNumberOfTimesToRun.Value; iIndex++)
{
// Call the functions in a random order
switch ((int)mcRandom.Next(0, 10))
{
default:
case 0:
Inline();
Function();
Delegate();
MulticastDelegate();
break;

case 1:
Function();
Delegate();
MulticastDelegate();
Inline();
break;

case 2:
Delegate();
MulticastDelegate();
Inline();
Function();
break;

case 3:
MulticastDelegate();
Inline();
Function();
Delegate();
break;

case 4:
MulticastDelegate();
Delegate();
Function();
Inline();
break;

case 5:
Inline();
MulticastDelegate();
Delegate();
Function();
break;

case 6:
Function();
Inline();
MulticastDelegate();
Delegate();
break;

case 7:
Delegate();
Function();
Inline();
MulticastDelegate();
break;

case 8:
Inline();
Delegate();
Function();
MulticastDelegate();
break;

case 9:
MulticastDelegate();
Function();
Delegate();
Inline();
break;
}
}

// Display Inline Info
double dAverageTimesRanInline = (double)mlNumberOfTimesRanInline / (double)numericNumberOfTimesToRun.Value;
labelNumberOfTimesInline.Text = dAverageTimesRanInline.ToString("#.###");
labelNumberOfTimesInlinePercent.Text = "100.0%";

// Display Function Info
double dAverageTimesRanFunction = (double)mlNumberOfTimesRanFunction / (double)numericNumberOfTimesToRun.Value;
labelNumberOfTimesFunction.Text = dAverageTimesRanFunction.ToString("#.###");
float fPercent = (float)((dAverageTimesRanFunction / dAverageTimesRanInline) * 100.0f);
labelNumberOfTimesFunctionPercent.Text = fPercent.ToString() + "%";

// Display Delegate Info
double dAverageTimesRanDelegate = (double)mlNumberOfTimesRanDelegate / (double)numericNumberOfTimesToRun.Value;
labelNumberOfTimesDelegate.Text = dAverageTimesRanDelegate.ToString("#.###");
fPercent = (float)((dAverageTimesRanDelegate / dAverageTimesRanInline) * 100.0f);
labelNumberOfTimesDelegatePercent.Text = fPercent.ToString() + "%";

// Display MulticastDelegate Info
double dAverageTimesRanMulitcastDelegate = (double)mlNumberOfTimesRanMulticastDelegate / (double)numericNumberOfTimesToRun.Value;
labelNumberOfTimesMulticastDelegate.Text = dAverageTimesRanMulitcastDelegate.ToString("#.###");
fPercent = (float)((dAverageTimesRanMulitcastDelegate / dAverageTimesRanInline) * 100.0f);
labelNumberOfTimesMulticastDelegatePercent.Text = fPercent.ToString() + "%";
}

// Approach 1 - Code inline
private void Inline()
{
long lNumberOfTimesExecuted = 0;
long lAmountOfTimeToRunFor = (long)(numericLengthOfTimeToRun.Value * 1000);

CParticle cParticle = new CParticle();
Stopwatch cStopwatch = new Stopwatch();
cStopwatch.Start();

while (cStopwatch.ElapsedMilliseconds < lAmountOfTimeToRunFor)
{
lNumberOfTimesExecuted++;

cParticle.f1 += 1.5f;
cParticle.f2 = 15.567f;
cParticle.f3 -= 0.0001f;
cParticle.i4 += 3;
cParticle.i5 = 23345;
cParticle.i6 -= 7;
cParticle.s7 = "Hello";
cParticle.s8 += "A";
cParticle.l9 += 123;
}

mlNumberOfTimesRanInline += lNumberOfTimesExecuted;
}

// Approach 2 - Code in a function call
private void Function()
{
long lNumberOfTimesExecuted = 0;
long lAmountOfTimeToRunFor = (long)(numericLengthOfTimeToRun.Value * 1000);

CParticle cParticle = new CParticle();
Stopwatch cStopwatch = new Stopwatch();
cStopwatch.Start();

while (cStopwatch.ElapsedMilliseconds < lAmountOfTimeToRunFor)
{
lNumberOfTimesExecuted++;

Update(cParticle);
}

mlNumberOfTimesRanFunction += lNumberOfTimesExecuted;
}

// Approach 3 - Code in a function call, called from a delegate
private void Delegate()
{
long lNumberOfTimesExecuted = 0;
long lAmountOfTimeToRunFor = (long)(numericLengthOfTimeToRun.Value * 1000);

UpdateDelegate MyDelegate = new UpdateDelegate(Update);

CParticle cParticle = new CParticle();
Stopwatch cStopwatch = new Stopwatch();
cStopwatch.Start();

while (cStopwatch.ElapsedMilliseconds < lAmountOfTimeToRunFor)
{
lNumberOfTimesExecuted++;

MyDelegate(cParticle);
}

mlNumberOfTimesRanDelegate += lNumberOfTimesExecuted;
}

// Approach 4 - Code in several function calls, each called from a multicast delegate
private void MulticastDelegate()
{
long lNumberOfTimesExecuted = 0;
long lAmountOfTimeToRunFor = (long)(numericLengthOfTimeToRun.Value * 1000);

UpdateDelegate MyDelegate = null;
MyDelegate += new UpdateDelegate(Update1);
MyDelegate += new UpdateDelegate(Update2);
MyDelegate += new UpdateDelegate(Update3);
MyDelegate += new UpdateDelegate(Update4);
MyDelegate += new UpdateDelegate(Update5);
MyDelegate += new UpdateDelegate(Update6);
MyDelegate += new UpdateDelegate(Update7);
MyDelegate += new UpdateDelegate(Update8);
MyDelegate += new UpdateDelegate(Update9);

CParticle cParticle = new CParticle();
Stopwatch cStopwatch = new Stopwatch();
cStopwatch.Start();

while (cStopwatch.ElapsedMilliseconds < lAmountOfTimeToRunFor)
{
lNumberOfTimesExecuted++;

MyDelegate(cParticle);
}

mlNumberOfTimesRanMulticastDelegate += lNumberOfTimesExecuted;
}

// Function containing code to run
private void Update(CParticle cParticle)
{
cParticle.f1 += 1.5f;
cParticle.f2 = 15.567f;
cParticle.f3 -= 0.0001f;
cParticle.i4 += 3;
cParticle.i5 = 23345;
cParticle.i6 -= 7;
cParticle.s7 = "Hello";
cParticle.s8 += "A";
cParticle.l9 += 123;
}

// Functions containing code to run (spread across several functions)
private void Update1(CParticle cParticle)
{
cParticle.f1 += 1.5f;
}

private void Update2(CParticle cParticle)
{
cParticle.f2 = 15.567f;
}

private void Update3(CParticle cParticle)
{
cParticle.f3 -= 0.0001f;
}

private void Update4(CParticle cParticle)
{
cParticle.i4 += 3;
}

private void Update5(CParticle cParticle)
{
cParticle.i5 = 23345;
}

private void Update6(CParticle cParticle)
{
cParticle.i6 -= 7;
}

private void Update7(CParticle cParticle)
{
cParticle.s7 = "Hello";
}

private void Update8(CParticle cParticle)
{
cParticle.s8 += "A";
}

private void Update9(CParticle cParticle)
{
cParticle.l9 += 123;
}

// If the user changed how long each approach should run for
private void numericLengthOfTimeToRun_ValueChanged(object sender, EventArgs e)
{
}

// If the user changed how many times each approach should be run
private void numericNumberOfTimesToRun_ValueChanged(object sender, EventArgs e)
{
}
}
}



##### Share on other sites
Quote:
 Original post by deadlydogI noticed that C# does not have an inline keyword for functions.

Most C++ compilers ignore the "inline" keyword when determining whether or not to inline a function. Why should C# be any different?

The real issue, though, is JIT compiling, which is ideal for dynamic dispatch situations in which the same function is always chosen. JIT engines optimize for this situation (as do C++ compilers, in some circumstances), meaning that the difference between a delegate call and explicitly inlined code is usually just a pointer comparison.

EDIT: Actually, now that I look at it, the .NET runtime doesn't seem to do this.

[Edited by - Sneftel on May 14, 2008 11:40:17 AM]

##### Share on other sites
You should perhaps read my blog posts on this very subject. Inlining in .Net is actually quite different than perhaps what you're used to.

##### Share on other sites
Quote:
Original post by Sneftel
Quote:
 Original post by deadlydogI noticed that C# does not have an inline keyword for functions.

Most C++ compilers ignore the "inline" keyword when determining whether or not to inline a function. Why should C# be any different?

Yes, I know. the "inline" keyword is more of a hint to the compiler, rather than a strict rule it must follow. I was just blown away by how fast the function calls are; for example, in my test both the inline approach and the function call approach can execute the code 22000 times per second, even though the function call approach is making 22000 more function calls than the inline approach. So it seems that calling a function takes virtually no time at all. However, as I pointed out above, this is when passing a single parameter to the function, and it's passed by reference. If you were passing 10 parameters by value to the function, that may slow things down a bit.......hmmmm, I think I'll try that out.

##### Share on other sites
Quote:
 Original post by deadlydogSo it seems that calling a function takes virtually no time at all.

This is definitely true when the function call is in a correctly predicted branch. Modern processors see the branch coming up and prefetch the branched-to instructions, meaning that execution continues similarly to if there was no branch at all.

##### Share on other sites
reference types are passed by reference. value types are passed by value. anything declared as a struct is a value type, while things declared class are reference types.

default behavior for reference type passing is "sort of" by reference but not exactly. if you assign your parameter value to a new instance, it creates a new reference and the value of the variable passed into the method remains unchanged. modifying members of the reference type parameter will work like a by reference call.

the following method is how you really pass by reference in C# for both reference and value types.

public void Foo(ref MyObject mo) {    // ...}

##### Share on other sites
Quote:
 Original post by -MadHatterreference types are passed by reference. value types are passed by value. anything declared as a struct is a value type, while things declared class are reference types.

You seem to be confusing reference types with reference passing. C# passes both value and reference types by value, unless the ref keyword is given. The remainder of your post shows that you understand the practical upshot of C#'s system, but you should get your terminology straight. There's nothing "sort of" about C#'s evaluation strategy. ref variables are passed by reference; everything else, value type or reference type, is passed by value.

##### Share on other sites
Quote:
Original post by Sneftel
Quote:
 Original post by -MadHatterreference types are passed by reference. value types are passed by value. anything declared as a struct is a value type, while things declared class are reference types.

You seem to be confusing reference types with reference passing. C# passes both value and reference types by value, unless the ref keyword is given. The remainder of your post shows that you understand the practical upshot of C#'s system, but you should get your terminology straight. There's nothing "sort of" about C#'s evaluation strategy. ref variables are passed by reference; everything else, value type or reference type, is passed by value.

So does this mean that it's faster to pass my class object with the ref keyword (since you say this is the only way to make it actually pass the reference (i.e. pointer in c++ speak) of the object, not a copy of it)?
Thanks

##### Share on other sites
Quote:
Original post by Sneftel
Quote:
 Original post by -MadHatterreference types are passed by reference. value types are passed by value. anything declared as a struct is a value type, while things declared class are reference types.

You seem to be confusing reference types with reference passing. C# passes both value and reference types by value, unless the ref keyword is given. The remainder of your post shows that you understand the practical upshot of C#'s system, but you should get your terminology straight. There's nothing "sort of" about C#'s evaluation strategy. ref variables are passed by reference; everything else, value type or reference type, is passed by value.

Heh, that's one thing that's so much easier to explain using C++ :D.

##### Share on other sites
Quote:
 Original post by deadlydogsince you say this is the only way to make it actually pass the reference (i.e. pointer in c++ speak) of the object, not a copy of it

He didn't say that at all. If you pass a reference type, the reference gets passed by value, NOT THE OBJECT. You only need to pass reference types by reference if you want to change the reference (NOT THE OBJECT) inside of the function.

##### Share on other sites
Quote:
Original post by Sneftel
Quote:
 Original post by -MadHatterreference types are passed by reference. value types are passed by value. anything declared as a struct is a value type, while things declared class are reference types.

You seem to be confusing reference types with reference passing. C# passes both value and reference types by value, unless the ref keyword is given. The remainder of your post shows that you understand the practical upshot of C#'s system, but you should get your terminology straight. There's nothing "sort of" about C#'s evaluation strategy. ref variables are passed by reference; everything else, value type or reference type, is passed by value.

Think I need to clear something up here before people get confused.

First of all we will go over some terms to make sure we are all on the same page.
reference type and value type - these are the two base types, value types are structs and other base types such as int, float, ect. When you use a value type a copy of the information is made and that is used. A reference type (classes) are used by reference, so when you use them the reference is passed around not the data itself.

reference parameters vs. value parameters. Unless you use the 'ref' keyword, the parameter for a method will be a value parameter, so the parameter will be passed by value.

So what does this mean to us? Well that is where it gets a little strange.

take the fallowing

DoSomething(myClass a);

That is a value parameter, so we are passing it by value but what does it really mean? It means that we are coping the data. So what data gets copied? The reference to a gets copied and passed in. So why is this important? Let me show you.

void DoSomething(myClass a)
{
a = new myClass();
a.Text = "Did something";
}

So we do this.

myClass first = new myClass();
first.Text = "Some text";

Now we call the function.

DoSomething(first);

What will be the value of first.Text when we are done? It will still be "Some text";

Now we call the functions using a function call like so; void DoSomething(ref myClass a);

What will the value of first.Text be when you are done? "Did something".

So why is that? Because in this case we are passing the acutal reference to first and not just a copy. In the first case we changed the reference and so we were no longer referencing 'first', in the second one we were always referencing 'first'.

I really hope that didn't confuse anyone. By value of a reference is just a copy of the reference and doesn't really matter unless you change the reference.

theTroll

##### Share on other sites
Quote:
 Original post by TheTrollBecause in this case we are passing the acutal reference to first and not just a copy.

No, it's because we pass the reference by reference.

##### Share on other sites
Quote:
 Original post by WashuYou should perhaps read my blog posts on this very subject. Inlining in .Net is actually quite different than perhaps what you're used to.

Now that was an interesting read. One thing popped in my mind though. In some "dumb" compilers you can optimize looping over an array sequentially by using running pointers:

for (int i = 0; i < count; i++){  x += array; // array: extra instructions for calculating the memory address of the value}

can be optimized into:

int* ptr = array;int* end = array + count;while (ptr < end){  x += *ptr;  ptr++;}

In an embedded audio processing project I did recently this simple optimization resulted in 10-20% fewer instructions for functions that were mainly made up of such loops.
Note though that the processing was relatively simple and was mainly made up of loops like these. The things that were done inside the loop every iteration was not much.

But what I was wondering is if the JIT will pick up on optimizations like these or if the "foreach" keyword can play a role here? A search on "foreach vs for loops" does not turn up anything that indicates this though, but I can't help but wonder.

Now, I do realize that for games this is less of an issue as the heavy number crunching does not depend on looping over arrays, but for video and audio processing this is more of an issue where you are constantly looping over buffers.

##### Share on other sites
Quote:
Original post by DevFred
Quote:
 Original post by TheTrollBecause in this case we are passing the acutal reference to first and not just a copy.

No, it's because we pass the reference by reference.

No, it works exactly as I described. Why don't you test it and you will be a bit shocked.

This exact thing bit me hard when I first started working in C#.

theTroll

##### Share on other sites
Quote:
Original post by TheTroll
Quote:
Original post by DevFred
Quote:
 Original post by TheTrollBecause in this case we are passing the acutal reference to first and not just a copy.

No, it's because we pass the reference by reference.

No, it works exactly as I described. Why don't you test it and you will be a bit shocked.

This exact thing bit me hard when I first started working in C#.

theTroll

No, it's because the reference is passed by reference.

I'm not saying it doesn't behave as you described, it's your explanation that's not correct. But it's possible that in the end, it's just a terminology issue.
But what is certain, is that it does work with a reference-to-a-reference-to-an-object.

##### Share on other sites
No, the reference is passed by-value, unless you specify 'ref'.

So you have a copy of the reference, not the reference. Now in most cases this does not matter, it works the same. The exception is if you try to create a new object based on that reference. Because it is a copy of the reference and not the original reference when you do a new you do create a new object, but it's reference is assigned to the copy of the reference not to the original one. So then you will loose what you just created.

This is covered under parameters in the C# Standard.

"12.1.4 Value parameters
A parameter declared without a ref or out modifier is a value parameter.
A value parameter comes into existence upon invocation of the function member (method, instance
constructor, accessor, or operator) to which the parameter belongs, and is initialized with the value of the
argument given in the invocation. A value parameter ceases to exist upon return of the function member
(except when the value parameter is captured by an anonymous method (§14.5.15.3.1) or the function
member body is an iterator block (§26))."

theTroll

P. S. Just for some mucilaginous information. DoSomething(myClass a) and DoSomething(ref myClass a) are different in relation to function overloading. So both would be allowed without giving you an error. Makes sense just never thought of it before.

##### Share on other sites
Quote:
Original post by TheTroll
Quote:
Original post by DevFred
Quote:
 Original post by TheTrollBecause in this case we are passing the acutal reference to first and not just a copy.

No, it's because we pass the reference by reference.

No, it works exactly as I described. Why don't you test it and you will be a bit shocked.

It does work as you described, and I never doubted that.

All I'm saying is that your terminology is a bit fuzzy. The reason it works as described is because if you use the ref keyword, the reference gets passed by reference instead of by value. This does not make the reference more "actual".

##### Share on other sites
With all the cold medication I am on right now, I am a bit surprised I was not talking about pink bunnies. Also typed that holding a 4 month old that is sick also. So yeah, the terminology could have been much better.

Just didn't wanted to try to make sure that folks understood there is a difference between passing by-reference and passing a reference by-value.

theTroll

##### Share on other sites
Quote:
 Original post by TheTrollthere is a difference between passing by-reference and passing a reference by-value.

That is the gist of it, yes.

The problem is that in C# and Java, pointers were (restricted and) renamed to "references", so there are certain types that consist of values called "references". Since these references are values, expressions can yield references, you can store references in variables etc.

In C++ we have a clear distinction between pointers and references. In C++, pointers are values (addresses of objects*), whereas references are not. A reference variable is just another name for some object, there are no references to references (because a reference is not an object) etc.

References were introduced in C++ to support call-by-reference semantics in operator overloading (so user-defined types could be used just as built-in types). The concepts "reference" and "call-by-reference" are closely related in C++.

In Java there is only call-by-value (primitive types are passed by value and reference types are passed by value), and there are no user-defined value types (classes always define reference types). So the term "reference" always means "reference to an object", which is a value.

In C#, there are user-defined reference types (classes) and user-defined value types (structs), and there is call-by-value and call-by-reference. You can
- pass value types by value (the object is copied)
- pass value types by reference (the original object is used)
- pass reference types by value (the reference is copied)
- pass reference types by reference (the original reference is used)

So you have to be very carefully when you talk about the word "reference" in C#. Do you mean the reference to an instance of a class (a value), or do you mean "call-by-reference" (a parameter passing mechanism involving the keyword ref)?

* In C++, an object is something that consumes memory and has an address. Don't confuse this with the term "object" in "OOP". I bet Zahlman can come up with a better definition though ;)

[Edited by - DevFred on May 14, 2008 3:06:21 PM]

##### Share on other sites
unlike java, you can still use normal pointers in .NET, but yea, they renamed managed pointers to handles in .net.