Reflection, Type.GetProperties and performance
April 13, 2010 Leave a comment
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(); } } }