Unity Reflection is Really Slow
Reflection allows you to introspect your code at runtime. You can do very dynamic things like call functions by their name as a string. As such, it’s a really powerful tool when you code needs to be more flexible. Unfortunately, it’s slow. Really slow. Today’s article puts it up against regular, non-reflection code to show the difference in speed. It’ll also walk you through reflection in C# in case you’ve never used it before. Read on to learn more about reflection in Unity!
C# reflection is mostly made available via the System.Reflection
namespace, but it starts with the System.Type
class. Every variable has a Type
in C# that you can access in two ways:
// When you want the Type of a class Type myClassType = typeof(MyClass); // When you want the Type of a variable Type myClassType = myClassInstance.GetType();
Once you have the Type
you can get its fields, properties, methods, and so forth. These are all defined by classes in System.Reflection
that end in “Info”: FieldInfo
, PropertyInfo
, MethodInfo
, etc. Here’s how you get them:
// Get the field named "MyField" FieldInfo myFieldInfo = myClassType.GetField("MyField"); // Get the field named "MyProperty" PropertyInfo myPropertyInfo = myClassType.GetProperty("MyProperty"); // Get the method/function named "MyMethod" MethodInfo myMethodInfo = myClassType.GetMethod("MyMethod");
Once you have these you can call methods on them to use them as though you had written code to do so directly. You can read from and write to fields and properties and call methods. Here’s how:
// Read or write a field from an instance of MyClass myFieldInfo.GetValue(myClassInstance); myFieldInfo.SetValue(myClassInstance, 123); // Read or write a property from an instance of MyClass // The last parameter is null for non-indexed properties myPropertyInfo.GetValue(myClassInstance, null); myPropertyInfo.SetValue(myClassInstance, 123, null); // Call a method on an instance of MyClass object[] parameters = new object[]{ 1, 2, 3 }; myMethodInfo.Invoke(myClassInstance, parameters);
With that in mind, have a look at this simple test comparing reflection to direct access. It measures the performance of all of the above functions.
using System; using System.Diagnostics; using System.Reflection; using UnityEngine; using UnityEngine.UI; public static class StopwatchExtensions { public delegate void TestFunction(); public static long RunTest(this Stopwatch stopwatch, TestFunction testFunction) { stopwatch.Reset(); stopwatch.Start(); testFunction(); return stopwatch.ElapsedMilliseconds; } } public class TestClass { public int IntField; public int IntProperty { get; set; } public void VoidMethod() { } } public class TestScript : MonoBehaviour { private const int NumIterations = 10000000; private string report; void Start() { var stopwatch = new Stopwatch(); Type testClassType = null; FieldInfo intFieldInfo = null; PropertyInfo intPropertyInfo = null; MethodInfo voidMethodInfo = null; int intValue = 0; var testClassInstance = new TestClass(); var getTypeTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { testClassType = typeof(TestClass); } }); var getFieldTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intFieldInfo = testClassType.GetField("IntField"); } }); var getPropertyTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intPropertyInfo = testClassType.GetProperty("IntProperty"); } }); var getMethodTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { voidMethodInfo = testClassType.GetMethod("VoidMethod"); } }); var readFieldReflectionTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intValue = (int)intFieldInfo.GetValue(testClassInstance); } }); var readPropertyReflectionTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intValue = (int)intPropertyInfo.GetValue(testClassInstance, null); } }); var readFieldDirectTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intValue = testClassInstance.IntField; } }); var readPropertyDirectTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intValue = testClassInstance.IntProperty; } }); var writeFieldReflectionTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intFieldInfo.SetValue(testClassInstance, 5); } }); var writePropertyReflectionTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { intPropertyInfo.SetValue(testClassInstance, 5, null); } }); var writeFieldDirectTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { testClassInstance.IntField = intValue; } }); var writePropertyDirectTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { testClassInstance.IntProperty = intValue; } }); var callMethodReflectionTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { voidMethodInfo.Invoke(testClassInstance, null); } }); var callMethodDirectTime = stopwatch.RunTest(() => { for (long i = 0; i < NumIterations; ++i) { testClassInstance.VoidMethod(); } }); report = "Test,Reflection Time,Direct Time\n" + "Get Type," + getTypeTime + ",0\n" + "Get Field," + getFieldTime + ",0\n" + "Get Property," + getPropertyTime + ",0\n" + "Get Method," + getMethodTime + ",0\n" + "Read Field," + readFieldReflectionTime + "," + readFieldDirectTime + "\n" + "Read Property," + readPropertyReflectionTime + "," + readPropertyDirectTime + "\n" + "Write Field," + writeFieldReflectionTime + "," + writeFieldDirectTime + "\n" + "Write Property," + writePropertyReflectionTime + "," + writePropertyDirectTime + "\n" + "Call Method," + callMethodReflectionTime + "," + callMethodDirectTime + "\n"; } void OnGUI() { var drawRect = new Rect(0, 0, Screen.width, Screen.height); GUI.TextArea(drawRect, report); } }
If you want to try out the test yourself, simply paste the above code into a TestScript.cs
file in your Unity project’s Assets
directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.10.2
- Unity 4.6.3, Mac OS X Standalone, x86_64, non-development
- 640×480, Fastest, Windowed
And got these results:
Test | Reflection Time | Direct Time |
---|---|---|
Get Type | 4 | 0 |
Get Field | 2778 | 0 |
Get Property | 6215 | 0 |
Get Method | 5570 | 0 |
Read Field | 2358 | 6 |
Read Property | 5326 | 7 |
Write Field | 2261 | 8 |
Write Property | 6670 | 7 |
Call Method | 4452 | 4 |
As the title of this article says, the reflection versions of all of these functions are really slow. The first four—GetType
, GetField
, GetProperty
, and GetMethod
—are completely free in the direct versions because there’s literally nothing to do: you just use the class like normal. GetType
is quite fast, but the other methods are extremely slow.
In the remaining tests—read, write, and call—the reflection versions are still extremely slow compared to their direct alternatives. Even with a huge number of iterations it was difficult to even get the direct versions to register. Their test numbers are so low that it’s tough to draw exact conclusions. All of the reflection operations are roughly 1000x slower than the direct code that doesn’t use reflection.
In short, use reflection very sparingly. A few times per frame isn’t going to be a big deal, but thousands of times will. If you have to use it, make sure to cache the results of functions like GetMethod
. They’re very expensive and won’t be changing during runtime anyhow. It’s easy to get a 2x speedup by simply changing obj.GetMethod("Foo").Invoke(obj, params)
to cachedFooMethod.Invoke(obj, params);
.
That’s all for today. If you’ve got any thoughts about reflection in C#, feel free to leave a comment below!
#1 by @junkdogAP on April 18th, 2015 ·
Hi, interesting read, but those results are alarming… Are you sure the benchmark itself isn’t what’s causing heavily skewing the results in favor of direct access? I don’t have any experience writing microbenchmarks in C#/mono, but 1000x performance loss doesn’t sound right.
I benchmarked constructor invocation in java before; granted constructor invocation is simpler than method invocation (different VM operations, for one), but in java the performance difference between reflection vs direct was only 4x: https://gist.github.com/junkdog/deee0260b1b6bb7ea0af – https://github.com/EsotericSoftware/reflectasm/issues/23 is perhaps more interesting, and the perf impact from reflection comes nowhere near 1000x.
It’s entirely possible java’s reflection is considerably better optimized than Mono’s, but by that much?
#2 by jackson on April 18th, 2015 ·
It’s always possible that there’s something wrong with the test itself. I don’t see it in this case, but perhaps you or another reader will spot something.
One thing to keep in mind is that Unity’s implementation of .NET is based on an old version of Mono, itself an unofficial port of the original Microsoft .NET implementation. It has quite different characteristics than other .NET implementations, even the version of Mono that it was forked from and Mono today. Those differences surely include performance in addition to features and bugs. Could they account for this difference? It’s hard to say without doing the same test on a stock version of Mono and Microsoft .NET. If you do such a test, please post your results here.
#3 by DoesntMatter on October 24th, 2015 ·
You can easily increase the perormance of reflection when you create delegates with the Delegate.CreateDelegate method. This will give you far better results https://msdn.microsoft.com/de-de/library/53cz7sc6(v=vs.110).aspx
You should really reconsider the title of this article here.
#4 by jackson on October 24th, 2015 ·
The purpose of the test was to determine the speed of reflection, not delegates. That said, I did use a delegate to clean up the test code and it’s possible that it skewed the results. So I rewrote the test to remove the delegate:
And re-ran the test on the same computer with Unity 5.2.1. Here are the results:
As you can see, the results are broadly the same as in the original article. While I don’t think the delegate skewed the results, I do find your suggestion of using
Delegate.CreateDelegate
intriguing. Perhaps I’ll write an article on that subject as a follow-up to your comment. Thanks for the idea!#5 by Dylan on October 31st, 2015 ·
knowing only basic concepts of reflection (and a little about Unity’s origin), I can’t say I’m very surprised by the results. I love the level of detail and explanation you provide, and thanks for the advice of caching reflection results!
#6 by Leonardo on September 6th, 2017 ·
I was able to run this 4000 times per frame, maintaining 60fps. Above this the fps begins to fall.
Let’s face it, 4,000 times is a LOT.:
#7 by jackson on September 6th, 2017 ·
Sure, that sounds totally believable. You basically did one “Get Type”, one “Get Method”, and one “Call Method” per frame. Per the results in the article, those took me 4 milliseconds, 5570 milliseconds, and 4452 milliseconds for the 10,000,000 times I called them. That’s a total of 10,026 milliseconds. If you divide that total by 10,000,000 you get 0.0010026 per iteration of your loop. Then you looped 4,000 times per frame which would have taken my test computer 4.0104 milliseconds. At 60 FPS, you have a per-frame budget of 1000 / 60 ~= 16.67 milliseconds. So 4.0104 represents ~24% of your total frame time on my test computer.
Is that a good use of 24% of your frame budget? I’d argue that it isn’t. I’d much rather spend the time on physics, logic, and AI than reflection. Personally, I wouldn’t even spend 2% or 0.2% on reflection because I want to spend the computer’s resources on things the player cares about. So I stand by my closing advice from the article:
#8 by Matthew Miller on April 11th, 2020 ·
This is a great breakdown. I was always told reflection is much slower, but seeing the numbers is eye opening. It’s def something we do not want to do every frame. Thanks for this!
#9 by N on May 27th, 2020 ·
Very helpful, thank you!
#10 by Scott Purcival on November 11th, 2020 ·
Is it possible that your results are being skewed due to compiler optimisations?
I see that you are assigning the same variable over and over again, perhaps in the direct assignments the compiler optimises this down to one assignment, whereas in the reflected assignments it cannot make such an assumption and therefore has to assign all 10,000 times.
Perhaps you could mark intValue as volatile or assign i each iteration to make sure such optimisations are not occurring?
#11 by jackson on November 12th, 2020 ·
It’s possible, but difficult to say conclusively after five years and so many changes since Unity 4.6.3. This was run on Mono, not IL2CPP or Burst as today’s code would be. Was Mono able to make such an optimization? Given its irrelevancy in today’s Unity, the answer probably isn’t worth spending time investigating.
Still, we can look at the “Direct Time” results and make some informed guesses based on the data. Presumably, a loop that’s replaced with a single assignment due to such an optimization would have taken 0-1 milliseconds. The only tests taking 0 milliseconds were the ones that were hard-coded with that result. “N/A” or a blank cell would have probably been more appropriate.
Instead, we’re seeing variation from 4 to 8 milliseconds. For some reason, I got even higher numbers without the delegates in this comment. So while we don’t know for sure, it seems unlikely that the compiler was optimizing away these loops.