Reflection, Type.GetProperties and performance

In C#, you can use reflection to get a list of properties and fields for a type – which can be very useful when comparing objects for instance, or creating automated tests. However, if you’re repeatedly using GetProperties or GetFields, you should probably cache the results, because the call if fairly slow.

Caching can be done using a dictionary, for instance.

Performance Comparison

Running GetProperties 233.900 took 1351 ms (5.7 µs/call), which in turn means that we can call GetProperties 173.131 times per second. That might sound really fast, but using a cached version where we use a Dictionary<,> to perform the caching, 233.900 calls took 17 ms (0,07 µs /call), meaning that we can call our cached method 13.758.823 times per second!

The cached method is 80 times faster. If you use complex binding flags, the differences in the results are bound to be even greater! But hold on, the case isn’t that clear cut. Read on!

When to Optimize

Should you optimize? Well, that depends, because in this case, optimizing through caching;

  • reduces the flexibility of the code
  • increase the complexity of the code
  • increases the memory footprint (marginally)

80 times faster seems like a big difference, but if you’re going to use PropertyInfo.GetValue to access the properties that were returned, this optimization will be dwarfed by the performance implications of GetValue – see my post about get value.

Each GetProperties takes 5.7 µs – that’s the non-optimized version. Plenty fast. Say that that call returns 10 properties and you then proceed to call GetValue for each of those, that’ll be 10 calls at 7.4 µs a pop (computed from my link above), or 74 µs. So even if you optimize GetProperties to 70 ns, the optimized version runs at 74 µs instead of about 80 µs. Not a big save for the added complexity.

If you’re calling GetProperties a small, intermediate or even large number of times, don’t use this optimization!

If you call it a huge number of times, millions or billions of times throughout the execution of your application, you could consider using caching. Or, perhaps, you should rethink your design choices completely…

The Source Code

You’ll find my benchmark code below.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                const int iterations = 100;

                Console.WriteLine("Comparing Property Get Methods");
                long direct = TestGetPropertiesDirect(iterations);
                long cached = TestGetPropertiesCached(iterations);
                Console.WriteLine(
                    "Cached access is {0} times faster than reflection access",
                    1.0 * direct / cached);

                Console.WriteLine();
                Console.WriteLine();

                Console.WriteLine("Done, press r to go again!");
                if (Console.ReadKey().KeyChar != 'r')
                {
                    return;
                }
            }
        }

        private static long TestGetPropertiesDirect(int iterations)
        {
            Console.WriteLine("TestGetPropertiesDirect...");

            List<Type> types = GetTestTypes();
            Stopwatch sw = Stopwatch.StartNew();
            int gets = 0;
            for (int i = 0; i < iterations; i++)
            {
                foreach (Type type in types)
                {
                    List<PropertyInfo> properties = type.GetProperties().ToList();
                    gets++;
                }
            }

            Console.WriteLine(
                "  {0} iterations took {1} ms, {2} iterations/s",
                gets,
                sw.ElapsedMilliseconds,
                gets / (sw.ElapsedMilliseconds / 1000.0));

            return sw.ElapsedMilliseconds;
        }

        private static Dictionary<Type, List<PropertyInfo>> _propertyDictionary = 
            Dictionary<Type, List<PropertyInfo>>();

        private static long TestGetPropertiesCached(int iterations)
        {
            Console.WriteLine("TestGetPropertiesCached...");

            Stopwatch sw = Stopwatch.StartNew();
            List<Type> types = GetTestTypes();
            int gets=0;
            for (int i = 0; i < iterations; i++)
            {
                foreach (Type type in types)
                {
                    List<PropertyInfo> properties = GetCachedProperties(type);
                    gets++;
                }
            }

            Console.WriteLine(
                "  {0} gets took {1} ms, {2} iterations/s",
                gets,
                sw.ElapsedMilliseconds,
                gets / (sw.ElapsedMilliseconds / 1000.0));

            return sw.ElapsedMilliseconds;
        }

        private static List<PropertyInfo> GetCachedProperties(Type type)
        {
            List<PropertyInfo> properties;
            if (_propertyDictionary.TryGetValue(type, out properties) == false)
            {
                properties = type.GetProperties().ToList();
                _propertyDictionary.Add(type, properties);
            }

            return properties;
        }

        // Returns a list of types to use for the subsequent tests
        private static List<Type> GetTestTypes()
        {
            return
                typeof(string)
                    .Assembly
                    .GetTypes()
                    .ToList();
        }
    }
}

About mfagerlund
Writes code in my sleep - and sometimes it even compiles!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: