I often get the VS comment to change the following, since GetComponent (while null) can generate garbage
var comp = GetComponent<Transform>();
if (comp != null) ReallyImportantCode.DoSomething<Transform>(comp);
into
if (TryGetComponent<Transform>(out var comp))
ReallyImportantCode.DoSomething<Transform>(comp);
When benchmarking the code with the code below, I get that GetComponent with the component being null has terrible editor performance, but fine build performance, with no garbage allocated.
The implementation is very naive, but I wondered if this is something that has changed over the years.
Testing code:
Profiler.BeginSample("GetComponent True");
for (int i = 0; i < RepetitionsPerFrame; i++)
{
var comp = GetComponent<Transform>();
if (comp != null) ReallyImportantCode.DoSomething<Transform>(comp);
}
Profiler.EndSample();
Profiler.BeginSample("TryGetComponent True");
for (int i = 0; i < RepetitionsPerFrame; i++)
{
if (TryGetComponent<Transform>(out var comp))
ReallyImportantCode.DoSomething<Transform>(comp);
}
Profiler.EndSample();
Profiler.BeginSample("GetComponent False");
for (int i = 0; i < RepetitionsPerFrame; i++)
{
var comp = GetComponent<Animator>();
if (comp != null) ReallyImportantCode.DoSomething<Animator>(comp);
}
Profiler.EndSample();
Profiler.BeginSample("TryGetComponent False");
for (int i = 0; i < RepetitionsPerFrame; i++)
{
if (TryGetComponent<Animator>(out var comp)) ReallyImportantCode.DoSomething<Animator>(comp);
}
Profiler.EndSample();
Correct, and I can’t remember if this was ever different.
What I find interesting from your benchmark and what should be the reason to use TryGetComponent for you, is that both failing and succeeding the getting the component it is more performant than successfully getting it with GetComponent. So unless you expect to nearly always fail getting it, it would be the more performant call on average.
Also, unless your checking for a MonoBehaviour or if that component is assigned to a MB field in the inspector, the first time any script accesses a component this way the Managed Shell for that object WILL be allocated then. It’ll be retained for (at least) the lifetime of the component and reused on every subsequent access, so it’s not directly “garbage” but that allocation will have some performance cost.
Oh and something that DID change: those EditorOnly [<marker name>] entries used to be just GC.Alloc samples under the GetComponent call that would add their GC Alloc amount to the parent scopes. To help guide optimization efforts towards what actually matters for performance in a build, we introduced these EditorOnly markers that “take the blame” for the GC Alloc and do not propagate it up the chain. Otherwise that GetComponent False marker and it’s parents would be shown to inherit nearly 6MB in the GC Alloc column.
The EditorOnly markers can’t “consume” the time taken for them in the same manner though as that would mess with the totals.
On the flip side, that GC Alloc hiding + GC Alloc happening across all threads sometimes leads to confusion as the Main Threads totals for GC Alloc and the Memory Profiler GC Allocated In Framed totals across all threads not matching up is it’s own source of confusion… I guess you can’t win on all fronts…
If I’m not mistaken, any temp variable declared like this, will create garbage. Since “var comp” is not cached, or saved in any way.
But how much garbage to collect, or how un-performant it exactly is? I’m not sure. I use temp variables quite regularly, so I can’t agree that it’s a major issue.
It’s definitely not a major issue if you don’t do it everywhere.
Was running some comparison in benchmarking and found the difference between build and editor interesting
Assigning a reference to a local variable doesn’t inherently allocate. In this case if you’re getting a reference to something already present, there’s no allocation. If you’re, say, instancing a class, then you will allocate something.
As Martin said though, if you’re accessing a Unity object for the first time it will allocate a managed shell, but not on subsequent accesses.
The tooltip in Visual Studio is bad here. I noticed it immediately when I read the blog post by @jbevain-msft - the second screenshot in the article reads “GetComponent allocates even if no component is found”, which is an editor-only issue, so it’s slightly wrong. Somebody should probably update the relevant package
That’s a super interesting link. After all these years, I had not realized that the C# wrapper for builtin types was created on-demand rather than when the native object was loaded. It totally makes sense as an optimization, so I’m not very surprised, but for some reason I just assumed it was the other way around. You learn something new every day!
This does mean that this could influence the result of @DevDunk 's test - since GetComponent is tested before TryGetComponent, it’s the first GetComponent call that generates the managed shell, so the cost of that goes into the GetComponent profiler scope. So if GetComponent and TryGetComponent were switched, you’d get a bit more time in TryGetComponent and a bit less in GetComponent. To get the correct results, you should probably call GetComponent outside of the profiler scope one time in order to remove that cost.
I hadn’t seen this yet (haven’t been profiling in editor in newer versions of Unity), but it’s really cool! That should help people a ton with understanding what’s going on, so thanks for introducing that.
I also noticed this recently when doing some performance tests. It seemed to me that TryGetComponent was strictly better in terms of performance, which is nice because I like methods with the bool TrySomething(out T) syntax.
If I’m not mistaken, the “TryOut” method creates no garbage. So if handling tons of these in a frame, garbage collection would surely hinder performance. Although I’m not 100% sure if it creates no garbage, just thinking aloud.
In builds GetComponent also generates no immediate garbage it seems. Just in editor.
So it mostly is a readability thing (?)
I always used TryGetComponent if I use null checks, like the analyzer suggests
I think it is passed by reference, not value.
The only difference with TryGetComponent would be that when the value is null it would not continue to run the code, but when the value is not null it should do the same, since it’s not manually managing memory like with Using
That’s a good point. But if temp-ing a script with GetComponent() is garbage free, that will give me more flexibility in future projects. thanks for the info.
TryGetComponent and GetComponent basically do the same thing under the hood, just the former ends in a Unity object null check. From the source code.
GetComponent<T>
[System.Security.SecuritySafeCritical]
public unsafe T GetComponent<T>()
{
var h = new CastHelper<T>();
GetComponentFastPath(typeof(T), new System.IntPtr(&h.onePointerFurtherThanT));
return h.t;
}
TryGetComponent<T>(out T)
[System.Security.SecuritySafeCritical]
public unsafe bool TryGetComponent<T>(out T component)
{
var h = new CastHelper<T>();
TryGetComponentFastPath(typeof(T), new System.IntPtr(&h.onePointerFurtherThanT));
component = h.t;
return h.t != null;
}
So all in all, GetComponent + null check, compared to TryGetComponent, should probably have the same performance impact, editor allocation aside.
From the source code you posted it’s not clear that they do “basically the same thing” - one calls GetComponentFastPath() internally and the other calls TryGetComponentFastPath(), which we know nothing about from those excerpts.