Original Post
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();
UpdateShownTimeNeeded();
}
// Show how long the test will take to run using the given duration and number of times to run
private void UpdateShownTimeNeeded()
{
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)
{
UpdateShownTimeNeeded();
}
// If the user changed how many times each approach should be run
private void numericNumberOfTimesToRun_ValueChanged(object sender, EventArgs e)
{
UpdateShownTimeNeeded();
}
}
}