Why is there such a large difference in the performance of different ways to pass delegates?

.net, c#, delegates, performance

Solution

After fixing up your code to actually call `ToString()` / `stringFunction()`, and measuring using Mono 2.10.9:

`ComputeStringFunctionViaFunc(objectArray[i].ToString);` is slow because `object.ToString` is virtual. Each object is checked in case it overrides `ToString` and the overridden `ToString` should be called. Your other delegates are created to refer to a non-virtual function (fast), which directly calls a virtual function (also fast). The fact that this is the cause can be seen when modifying the generated IL to change

ldelem.ref
dup 
ldvirtftn instance string object::ToString()

to

ldelem.ref
ldftn instance string object::ToString()

which always refers to `object.ToString`, never an overriding function. The three methods then all take about the same time.

Update: one additional method, to bind directly to `objectArray[i]` but still call `ToString` virtually:

for (int i = 0; i < objectArray.Length; ++i)
{
    ComputeStringFunctionViaFunc(objectArray[i].ToStringHelper);
}

static class Extensions
{
    public static string ToStringHelper(this object obj)
    {
        return obj.ToString();
    }
}

also gives roughly the same timings as the other non-virtual delegates.

Problem

I was attempting to compare three different ways of passing a delegate to a function in C# -- by lambda, by delegate, and by direct reference. What really surprised me was the direct reference method (i.e. `ComputeStringFunctionViaFunc(object[i].ToString))` was six times slower than the other methods. Does anyone know why this is? The complete code is as below: ``` using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.CompilerServices; namespace FunctionInvocationTest { class Program { static void Main(string[] args) { object[] objectArray = new object[10000000]; for (int i = 0; i < objectArray.Length; ++i) { objectArray[i] = new object(); } ComputeStringFunction(objectArray[0]); ComputeStringFunctionViaFunc(objectArray[0].ToString); ComputeStringFunctionViaFunc(delegate() { return objectArray[0].ToString(); }); ComputeStringFunctionViaFunc(() => objectArray[0].ToString()); System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch(); s.Start(); for (int i = 0; i < objectArray.Length; ++i) { ComputeStringFunction(objectArray[i]); } s.Stop(); Console.WriteLine(s.Elapsed.TotalMilliseconds); s.Reset(); s.Start(); for (int i = 0; i < objectArray.Length; ++i) { ComputeStringFunctionViaFunc(delegate() { return objectArray[i].ToString(); }); } s.Stop(); Console.WriteLine(s.Elapsed.TotalMilliseconds); s.Reset(); s.Start(); for (int i = 0; i < objectArray.Length; ++i) { ComputeStringFunctionViaFunc(objectArray[i].ToString); } s.Stop(); Console.WriteLine(s.Elapsed.TotalMilliseconds); s.Reset(); s.Start(); for (int i = 0; i < objectArray.Length; ++i) { ComputeStringFunctionViaFunc(() => objectArray[i].ToString()); } s.Stop(); Console.WriteLine(s.Elapsed.TotalMilliseconds); Console.ReadLine(); } [MethodImpl(MethodImplOptions.NoInlining)] public static void ComputeStringFunction(object stringFunction) { } public static void ComputeStringFunctionViaFunc(Func<string> stringFunction) { } } } ```

Original source