Optimizing C# reflection method calls

2017-03-08 14:35:20 Coding C#

TL;DR: Func<> and Action<> instead of MethodInfo.Invoke runs 50 times faster

I can recall the excitement when I first learnt about reflection in C#. At that time my programming experience was limited to basic VB6 and C, so being able to browse type information was completely new to me. I was shocked and inspired by the possiblities that reflection can bring.

Reflection enables a lot of applications. For example, GameObject.SendMessage in Unity is fulfilled by finding the method with a name via reflection and then invoking it. Another important application is generic serializers. Without reflection, serializing objects will involve the pain of writing similar routines for every class[1]. Generic serializers like XmlSerializer and BinaryFormatter have saved a huge amount of development effort.

[1] Or some messy template and macro magic in C++ – see this StackOverflow answer for more.

However, we may overlook the performance cost as we enjoy the convenience. The rest of this post measures the reflection method calls in C#, and propose an optimization with delegate.

The scenario, problem and answer

Assume that you are writing a serializer[2]. You will need to get the property values of the object using reflection, something like:

foreach (var prop in typeof(obj).GetProperties())
{
    var value = prop.GetMethod.Invoke(obj, emptyArray);
    WriteValue(value);
}

Intuitively, since getting properties from a class seems time consuming, you will cache the properties of the class:

// initialize once
_cachedPropertyLists[type] = type.GetProperties();

// somewhere repeatly executed
foreach (var prop in _cachedPropertyLists[typeof(obj)])
{
    var value = prop.GetMethod.Invoke(obj, emptyArray);
    WriteValue(value);
}

After some profiling, you will realize that the MethodInfo.Invoke call slowing your serializer down. This is an unfortunate pitfall of reflection – it is slow.

But why is that? Well, each time you call Invoke, it needs to find where the method is via some reflection APIs. With this knowledge in mind, we can find an direction to optimization: is it possible to cache the location of the method?

The answer, if you come from C or C++, is immediately function pointers. In C#, they are Delegates. More specifically, in this case we need Func<,> (or Action<,> if you are deserializing and using setters). The concept shall be fairly simple and can be Googled so I would not go into it. What matters is how to create a delegate from a MethodInfo:

// initialize once
var func = (Func<T1, T2>) Delegate.CreateDelegate(typeof(Func<T1, T2>), method);

// somewhere repeatly executed
var data = func(input);

Note that the type of the function pointer must match the method, or an exception will be thrown at run time.

How significant is this optimization? We can run a benchmark.

[2] This can happen. For example, when your boss wishes to avoid the poor performance of BinaryFormatter.

Benchmark

We have a data class:

class Data
{
    public int A { get; set; }
}

And two ways to get the value of property A (plus a non-reflection one for reference). Each method measures the time to get the value iterations times.

private static long GetWithMethodInfo(List<Data> list, int iterations)
{
    var getterA = typeof(Data).GetProperty("A").GetMethod;
    var sw = new Stopwatch();

    sw.Start();
    var sum = 0L;
    var param = new object[] { };
    for (int iter = 0; iter < iterations; ++iter)
    {
        for (int i = 0; i < DataSize; ++i)
        {
            sum += (int)getterA.Invoke(list[i], param);
        }
    }
    sw.Stop();

    Console.WriteLine($"Sum: {sum}");
    return sw.ElapsedMilliseconds;
}

private static long GetWithFunc(List<Data> list, int iterations)
{
    var func = (Func<Data, int>) Delegate.CreateDelegate(typeof(Func<Data, int>), typeof(Data).GetProperty("A").GetMethod);
    var sw = new Stopwatch();

    sw.Start();
    var sum = 0L;
    for (int iter = 0; iter < iterations; ++iter)
    {
        for (int i = 0; i < DataSize; ++i)
        {
            sum += func(list[i]);
        }
    }
    sw.Stop();

    Console.WriteLine($"Sum: {sum}");
    return sw.ElapsedMilliseconds;
}

private static long GetDirectlyFromProperty(List<Data> list, int iterations)
{
    var sw = new Stopwatch();

    sw.Start();
    var sum = 0L;
    for (int iter = 0; iter < iterations; ++iter)
    {
        for (int i = 0; i < DataSize; ++i)
        {
            sum += list[i].A;
        }
    }
    sw.Stop();

    Console.WriteLine($"Sum: {sum}");
    return sw.ElapsedMilliseconds;
}

To ensure fairness, only one of the methods is run in each application run:

static void Main(string[] args)
{
    // Parse input
    // ...
    // Generate data
    // ...
    // Go
    long ms = 0;
    switch (method)
    {
        case 0:
            ms = GetWithMethodInfo(list, iterations);
            break;
        case 1:
            ms = GetWithFunc(list, iterations);
            break;
        case 2:
            ms = GetDirectlyFromProperty(list, iterations);
            break;
        default:
            Console.WriteLine("Unrecognized method");
            return;
    }

    Console.WriteLine($"Ran {iterations} iterations in {ms}ms. Average: {(ms / (double)iterations):N4}ms");
}

And, the results:

Method Average Time (ms)
MethodInfo.Invoke 2.0960
Func 0.0380
Direct Get 0.0250

So, yes there is a huge difference between MethodInfo.Invoke and Func… a 50 times difference. Hopefully you know what to do next time you use reflection.

Happy coding!