Tuesday, September 19, 2017

Unity3D Performance With InvokeRepeating()

The current project we're working on has a number of places where a Unity MonoBehavior's Update() function is basically just being used to keep a timer going, with every so often actual work happening.  I suggested moving stuff like this into a call to InvokeRepeating with an appropriate frequency, and generally met with some skepticism.  Research online led to the same sort of thing, with very few actual numbers and a few places where people treated it like a vampire treats sunlight.  "Reflection! Hissss!"

These kind of reactions sounded extreme to me, and "it's slower" doesn't tell you much, so I wanted to actually measure it.

I created an empty Unity project, and created a scene with 2100 static objects arranged in a grid.  Each object is a textureless sphere, and has one component called PerformanceTest.  The component is written in C#.  This component contains a start() method, and another method that does a few useless things just so it's not empty: Allocate a Vector3(), check its magnitude, multiply that result up and down a few times.  This method is either Update() or UpdateInvokeRepeating() depending on the test.  I also ran a test with no update method present at all, just an empty start() method.

When InvokeRepeating is used in this test, their timings and delays are all set the same, to make the difference as obvious as possible.  With 2100 of these in the scene it's pretty easy to see results in the Unity profiler.  These were tested on a Ryzen 1600 running at 3.3 GHz.

Nothing Startup: CoroutinesDelayedCalls 3.78ms, containing PerformanceTest.Start() 1.26ms
Update() Startup: CoroutinesDelayedCalls 3.78ms, containing PerformanceTest.Start() 1.26ms
InvokeRepeating() Startup: CoroutinesDelayedCalls 5.23ms, containing PerformanceTest.Start() 2.75ms

You can see here that upon startup, calling InvokeRepeating 2100 times cost approximately 1.5ms.  The other two startup methods were present, but totally empty.  Since the method name is provided as a string, this is the point where a search will have to happen, so this could potentially get slower if you have a lengthy class.

Nothing Update: BehaviorUpdate 0.0ms (of course)
Update() Update: BehaviorUpdate 2.34ms, containing PerformanceTest.Update() 0.92ms
InvokeRepeating() Update: CoroutinesDelayedCalls 3.44ms, containing PerformanceTest.UpdateInvokeRepeating () 0.94ms

Stacking all the InvokeRepeating calls does indeed lose to a straight Update, with the overhead appearing to be roughly 50% higher than Update has.  Notable is that CoroutinesDelayedCalls is not present in the list on frames where no invocations occur, and the Unity generic "Overhead" does not meaningfully change either.  There isn't an obvious ongoing performance penalty between invocations.

What this suggests to me is that unless your repeated invocation is more frequent than every other frame, you're probably getting a net win by using InvokeRepeating rather than Update() if possible.  While that may not be worth it if you need to have the overhead of Update() being present anyway, it doesn't have a dramatic startup cost and its additional overhead is easily overcome if your component doesn't need to update every frame.