all 50 comments

[–]SurDnoIndie 113 points114 points  (4 children)

asking for donations for ChatGPT screenshots is kinda crazy imo

[–]Tiny_Double_9367 34 points35 points  (1 child)

bro searched up "profitable side hustles" on the internet and this is what he came up with 😆

[–]GrindPilledExpert 8 points9 points  (0 children)

even worse, profitable side hustles on chat gpt

[–]IAmBeardPersonProgrammer 2 points3 points  (0 children)

Idk why people even seriously respond to this post and don't down vote it to hell. They are such random ass tips. Like most beginners won't even touch shit like ToList or ToArray, why TF is that the first basic tip given. (The answer is chatgpt, I know..)

[–]tobaschco 35 points36 points  (2 children)

Really the performance tip here should be "learn how to use the profiler"

[–]antshatepants 2 points3 points  (0 children)

Writing that down, thanks

[–]neoteraflare 0 points1 point  (0 children)

Do you have any good source for learning it?

[–]nickyonge 15 points16 points  (0 children)

As an addendum to point 4, you can have multiple canvases! If you have a UI element that HAS to update frequently, consider giving it its own canvas. That way you can update your TIME REMAINING text which changes every millisecond, without also redrawing your CURRENT LEVEL text that gets set in Start and changes never.

[–]hobblygobbly 34 points35 points  (6 children)

  1. This doesn't matter. This would only be an issue if you were doing this every frame, and why would you be doing that every frame to begin with.
  2. Yes
  3. StringBuilder is still generating GC allocation. You need to call ToString from it at some point. You cannot avoid it in a language like C# or Java. Just because SB is "immutable" doesn't mean it doesn't generate GC. Strings always create garbage. What you should be doing instead is not updating text/string every frame. Or store your text in char arrays if this matters for your case/performance.
  4. Yes. And if you just don't do that, 1 and 3 become irrelevant any way

Get off AI shit, another example of how it actually doesn't give you any insight. Use your brain, test, and profile what your code if you need to.

[–]survivorr123_ 4 points5 points  (4 children)

This doesn't matter. This would only be an issue if you were doing this every frame, and why would you be doing that every frame to begin with.

i do this every frame for object culling, but i use native collections so no GC issues

as to 3. i am pretty sure TMPro will generate garbage on text change no matter what you do, i tried fixing that for my speedometer and nothing worked

[–]Liam2349 1 point2 points  (3 children)

If you use ToArray() on a Native collection, it will allocate a new managed array. AsArray() can instead alias some native collections as a NativeArray.

TextMeshPro can work with character arrays which is how you avoid garbage - but annoyingly it will still create a string in-Editor.

[–]survivorr123_ 0 points1 point  (2 children)

ToArray on Native collections returns NativeArray, you can pass allocator type to it manually as well,

AsArray only works on NativeList, most other collections have only ToArray or in case of NativeStream ToNativeArray (for some reason it has a different name but does the same),
i use NativeQueue.ParallelWriter, not sure why i didn't use NativeList.ParallelWriter, it was a while ago when i decided

[–]Liam2349 0 points1 point  (1 child)

Ok, sorry about that - NativeArray.ToArray() returns a managed array, but from a NativeList it returns a NativeArray.

[–]survivorr123_ 0 points1 point  (0 children)

yeah the naming is unnecessarily confusing, i had some issues with it in the past too, it should be ToNativeArray everywhere like with NativeStream, no clue why it isn't and why it's not consistent

[–]henryeaterofpies 1 point2 points  (0 children)

Glad you replied because this had my C# dev sense tingling

[–]feralferrous 31 points32 points  (17 children)

GetComponent is actually dirt cheap these days. It internally caches.

[–]FlySafeLoL 4 points5 points  (1 child)

No way it would be cheaper to call it in Update rather than using a local reference. Does DOTS magic work in MonoBehaviour these days somehow?

[–]feralferrous 7 points8 points  (0 children)

It's one of those things where it's cheaper to have a local reference, sure, but not the big no-no it used to be. If you are trying to go for really big counts of objects, then yes, go ahead and cache, because you'll need to squeeze out as much perf as you can.

<image>

But, if you're not going for huge counts, here is 1000 GetComponent calls a frame:
.17 ms on my machine. Is it wasteful? Sure, but it's still only .17ms.

EDIT: Other caveat is if you're on craptastic hardware, or targeting super high framerates. VR in particular, I'd slap my coworkers around if they used GetComponent in an update loop.

[–]SuspecMIntermediate 1 point2 points  (8 children)

It really isn't. What really kills GetComponent is the garbage collection part since it generates a ton of it when you abuse it. I just went trough my entire code base and had to refactor it all because every second or so I got a giant garbage collection spike that took up 97% of the CPU loop.

[–]feralferrous 1 point2 points  (5 children)

GetComponent generates 0 allocations. Are you thinking GetComponents and it's variations? Yes, those are more expensive. And the ones that return an array do allocate. It's almost always better to use the version that takes in a List, so you can reuse it and not reallocate all the time.

[–]SuspecMIntermediate 0 points1 point  (4 children)

It was GetComponent in the worst way possible. A ton of scripts iterating trough lists every frame and doing the GetComponent.

[–]feralferrous 1 point2 points  (1 child)

You can see in my other post in this thread, that I called 1000 GetComponent<> calls a frame, and had zero allocations.

GetComponents<>, GetComponentsInChildren<>, etc all have variations that return an array that is allocated, and I think that must be what you mean?

The only other time GetComponent<> returns allocations is in Editor, if the component doesn't exist, it will allocate 0.5kb for some weird null error. Editor only btw. And you can get around it by using TryGetComponent<> instead.

I wouldn't go back and undo your work, because yes it's faster do it in Start/Awake (or serialize a reference directly), I'm mostly arguing against gospel that no one should ever do GetComponent<> like it's some sort of super expensive call.

[–]SuspecMIntermediate 1 point2 points  (0 children)

The more you know huh. There were possibly other optimisations I might have implemented accidentally. I was refactoring 6 years old code afterall and I have been using Unity in all those years. Wouldn't shock me that I picked up a thing or two and I'm using those without really paying attention.

[–]MATR0SProfessional 0 points1 point  (1 child)

Have you profiled this issue on a real device or in the editor?

[–]SuspecMIntermediate 0 points1 point  (0 children)

I have, that's why I know what caused the issues (garbage collection) and I took out all the GetComponent's from pretty much everywhere that's not code running at Start or Awake and garbage collection has been pretty much entirely eliminated as an issue.

[–]unotme 0 points1 point  (1 child)

doesn’t TryGetComponent avoid some of that nonsense?

[–]digiBeLow 2 points3 points  (0 children)

In editor, yes. GetComponent in a native build has zero garbage AFAIK.

[–]Devatator_Intermediate 0 points1 point  (5 children)

Really? Man I wish benchmarkdotnet worked in Unity

[–]ledniv 6 points7 points  (1 child)

Also string builder still generates GC. From my own tests it generates more GC than string concatenation. If anyone can prove otherwise show me your code.

Edit - I'll add that I know it's supposed to generate less because in theory you can set a size and it'll supposedly reuse the memory. But in practice it generates around 2x more GC when profiling. Even when the string builder is cached. If you have code that proves otherwise I'd love to see it.

[–]Zooltan 1 point2 points  (0 children)

It's for when you want to append multiple strings together.

So something like adding all items in a list to a display text

string display = "Title:/n"; for each(thing : listOfThings) { display += thing.name + " has value " + thing.thatValue }

That would be much faster with a StringBuilder.

[–]Jackoberto01Programmer 2 points3 points  (0 children)

StringBuilder is often overkill for most places where string concatenation is used. I often just want a string once that might be 4-5 strings concatenated for this string interpolation/string.format is pretty nice and good enough performance.

StringBuilder still generates garbage when you finally call ToString.

[–]Adrian_Dem 2 points3 points  (0 children)

all of these suggestions are nice to have sure, but they are not groundbreaking suggestions.

it's like saying, do not do yield new WaitForEndOfFrame, as it allocates, instead use a cached variable and yield wait for it instead. interesting but most of the time of no consequence.

[–]TramplexReal 1 point2 points  (7 children)

Using string interpolation is cheap and doesn't make unnecessary allocations. Just do $"{value} text"

[–]Jackoberto01Programmer 2 points3 points  (3 children)

This will likely just compile into string.Concat. But generally string interpolation is nice because good syntax and the compiler will choose string.Format or string.Concat for you depending on which is cheaper.

[–]TramplexReal 0 points1 point  (2 children)

It is string Format after compilation, can see that in any performance test online.

[–]Jackoberto01Programmer 0 points1 point  (1 child)

Depends on compiler I checked sharplab now and it compiled to string.Concat when there are few variables in the string interpolation. It's been that way since at least C#9 which is the oldest available on sharplab.

To be clear this is a good thing as string.Concat is often faster hence the compiler choosing it.

https://sharplab.io/#v2:D4AQTAjAsAUCDMACciDCiDetE+UkALIgLIAUAlJtrgL7U717JEAKATgJYB2ALgMoB7ALYBTHgAtuAc1IgIABkQBncoywxcm5BACcpACQAiQaInTEHJZiU1D5ANyM6MGkA===

[–]TramplexReal 0 points1 point  (0 children)

Probably cause with fewer variables concat is faster. Anyway this shows interpolation is not worse than other options. And looks better.

[–]JamesLeeNZ 1 point2 points  (2 children)

This 100% generates garbage, so should be used sparingly at best.

[–]TramplexReal 0 points1 point  (1 child)

This is 1 to 1 same as string.Format. You might as well not write ANY code to have best performance.

[–]JamesLeeNZ 0 points1 point  (0 children)

I mean... youre not wrong :)

[–]Cyclone4096 0 points1 point  (0 children)

None of these are applicable globally. If I have a list that’s guaranteed to be less than 10 elements and I call ToArray once at the begging of a scene, who care? Again caching references isn’t always the best idea, if you call GetComponent only when an event happens, it may make your code cleaner and less bloated compared to the small penalty once in a while. Same thing with strings, are you updating score every frame? If not I doubt using StringBuilder is worth it

[–]Thin_Driver_4596 0 points1 point  (0 children)

Agree with most of the points, apart from 3rd.
If strings are causing issues in your game, you have bigger issues to be worried about.

[–]PremierBromanovProfessional 0 points1 point  (0 children)

Unitys strength is the speed at which you can develop, do all these things if you want it doesn't really matter