Math is hard
Currently in JSD, the Seeking missiles are taking up WAY more cpu per frame than I would have anticipated. As much as 30% of the total cpu load per frame when there’s about 40-60 of them active. This is pretty much all its doing.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//increment 2 timers
if
(
timer
1
)
{
deactivate
self
activate
effect
}
if
(
no
target
)
{
range
rand
on
array
}
if
(
has
valid
target
)
{
get
pos
update
effect
}
2d
rotate
at
speed
to
face
target
translate
|
Part of the problem is gameobject compares aren’t trival,see. The translate and smooth rotate, are expensive. For almost all of the other objects in the game these happen during enable and never again. If I made the missile a ‘plasma ball’ then I could simplify the maths a bit here by not needing to update the orientation of the object .
SetActive()
I was doing a little bit of hunting around to find more on the cost of GameObject.Activate, which gets hit in the profiler for SetActive(true). I found this. Needless to say I had to test this theory that enable might be better than active. So Lets dig in.
This section is 3 methods running simultaneously so you can visualise the ratio of performance they require.
First up Instantiate and Destroy 100 simple boxes with RigidBody every frame.
I’ve highlighted the Destroy here too. Its separate as Destroy seems to add the item to a list Unity keeps internally and handles next frame (as I understand it.). Most of the memory here seems to be in native land not in GC land but we are still generating some garbage. Its also responsible for a big chunk of the ‘Other’ time, which I can only assume is Unity native code creating the GO, components and serialise, deserialising from the prefab.
Next, Deactivate 100 in a list, activate the next 100 in the list. This occilates each frame. This is the basics of an Object Pool.
Not a crazy amount faster but it is faster and there is no Destroy and no GC to speak of.
Ok lets try this enable disable trick.
Lets see them in isolation.
Instantiate and Destroy – 9.3ms to 10.3ms
Inst WITH the GC showing, ouch.
Activate/Deactivate – 6.7ms to 8.9ms
Not as good as we’d like but definitely better and no GC.
Enable/Disable on collider and renderer – 2.5ms to 3.0ms
Little bit of GC but this is insanely good, what’s going on.
Enable/Disable Generlised – 8.3ms – 15ms
Here I add the 2d variants, audio and animator components to the mix to create a more general solution, not the object being made just to the enable disable code, as activate/deactivate and instantiate/destroy would handle any combo and any number of components. In real world it would need any scripts you’ve added to the component to be disabled too, most likely.
GC is going crazy due to all of the GetComponent, remember that .rigidbody or .audio etc. are properties that basically end up calling GetComponent.
Ok, so once we try to extend the enable/disable trick to a more general solution it becomes worse than just instantiating and destroying. It is a good trick to remember though, if you had a very simple object it is a far more surgical method.
For the sake of science I tested this same set up for all modes with 10 and 500 cubes. Physics engine didn’t much care for 500 each, it actially made GameObject.Activate way more expensive than anything else. But the test was running at fractions of an fps due to way too many overlapping cubes.
With 10, it tells a different story.
Yes the order of best is the same, Inst about .8ms, Act about 0.7ms, Enab about 0.4ms. But those spikes, those spikes are where the Enable/Disable takes 7ms, yes SEVEN up from 0.4. It’s entirely down to it triggering a GC collect.
What if it only ever asks for things that do exist? I tried it with a few empty component scripts I create, no more GC. Remove them from the prefab, GC comes back. So if it only ever looks for components that do exist then its faster with no down sides. Its worth investigating a custom per prefab pool that uses this knowledge.
Well now I have to test what a find all Behaviours will do… Well that kinda sucks. Collider and Renderer are not behaviours, so you need to special case them. Something like
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Component
[
]
comps
=
go
.
GetComponents
<
Component
>
(
)
;
foreach
(
Component
c
in
comps
)
{
Behaviour
asBeh
=
c
as
Behaviour
;
if
(
asBeh
!=
null
)
asBeh
.
enabled
=
state
;
else
{
Collider
asCol
=
c
as
Collider
;
if
(
asCol
!=
null
)
asCol
.
enabled
=
state
;
else
{
Renderer
asRend
=
c
as
Renderer
;
if
(
asRend
!=
null
)
asRend
.
enabled
=
state
;
}
}
}
|
But that totally works and is nicer to look at, kinda. It runs at 0.3ms average but those spikes don’t go away, due to us now using GetComponents alloc’ing an array.
So not set active?
Well no, its a general solution that handles all cases, is mostly unintrusive to your codebase and can easily be written to never incur any GC Alloc. More over, its worst case is far more predictable and acceptable than the others in their general usage. Given that the GC is mostly out of our control and Unity doesn’t provide a no alloc way of fetching components the we cannot control the perf spikes due to the garbage being created. So unless you have a very specific set of components that you know exist and can access directly or already have around, you probably don’t want to enable/disable. All that being said, I’ve added ‘Intrustive enable disable pool object’ to my todo list.
Don’t do things you don’t need to
This isn’t at all Unity specific, its just general programming. What’s the best way of optimising ‘this’ code? Not doing it at all. For example, in some of my testing I wrote this;
1
2
3
4
5
6
7
|
if
(
go
.
renderer
!=
null
)
go
.
renderer
.
enabled
=
state
;
if
(
go
.
collider
!=
null
)
go
.
collider
.
enabled
=
state
;
if
(
go
.
collider2D
!=
null
)
go
.
collider2D
.
enabled
=
state
;
if
(
go
.
guiText
!=
null
)
go
.
guiText
.
enabled
=
state
;
if
(
go
.
audio
!=
null
)
go
.
audio
.
enabled
=
state
;
if
(
go
.
camera
!=
null
)
go
.
camera
.
enabled
=
state
;
if
(
go
.
animation
!=
null
)
go
.
animation
.
enabled
=
state
;
|
Works and looks fine. But it’s (potentially) doing twice as many GetComponents as it needs to. Changing it to this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Renderer
ren
=
go
.
renderer
;
Collider
col
=
go
.
collider
;
Collider2D
col2D
=
go
.
collider2D
;
GUIText
gt
=
go
.
guiText
;
AudioSource
au
=
go
.
audio
;
Camera
cam
=
go
.
camera
;
Animation
an
=
go
.
animation
;
if
(
ren
!=
null
)
ren
.
enabled
=
state
;
if
(
col
!=
null
)
col
.
enabled
=
state
;
if
(
col2D
!=
null
)
col2D
.
enabled
=
state
;
if
(
gt
!=
null
)
gt
.
enabled
=
state
;
if
(
au
!=
null
)
au
.
enabled
=
state
;
if
(
cam
!=
null
)
cam
.
enabled
=
state
;
if
(
an
!=
null
)
an
.
enabled
=
state
;
|
gave a non-trival performance improvement. Doesn’t lower GC Alloc as this seems to only come from requesting a Component that does not exist. It’s the same reason Unity recommends that you grab local references to any and all components to you need to access in your scripts during Start or OnEnable instead of during Update, FixedUpdate, etc.