Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Question Is there such a thing as too much component caching?

Discussion in 'Scripting' started by lejean, Aug 18, 2020.

  1. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    When I look at my character's scripts I feel like I have so many scripts that cache the same components in each script and I wonder if this actually has any impact on performance.

    Consider this scenario:

    I have 100 different scripts on a gameobject that all do something different but they all require a variable from script A.

    So in each of those 100 scripts I have to do a
    GetComponent<ScriptA>().variable
    .

    So let's say I have to use it alot and I want to cache it, so I create a variable for scriptA and do a
    Code (CSharp):
    1. ScriptA scriptA;
    2.  
    3. void Awake(){
    4. scriptA = getcomponent<ScriptA >();
    5. }
    6.  
    in each of those 100 scripts.

    Is there any downside in doing this or is there a better way?
    Does this make the loading of the game slower cause there are so many getcomponent calls in awake for example?
    Would it be better to just call the getcomponent instead of caching it (when used outside of update)?
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    The only way to assess performance is to attach the profiler.

    Conventional Unity3D wisdom holds that grabbing all those references in Start() instead of as you're going along is more performant, assuming they are invariant for the lifetime you know them to be.

    This seems intuitive, but I have never seen evidence or an experiment done that would prove or disprove it.

    Again though, if you're worried about performance when you are NOT having performance issues, that's not what I would consider a good use of engineering time, but your time is of course your own to spend. :)
     
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,947
    Often I don't do GetComponent caching. I just create serializable fields and assign them in the inspector. No GetComponent calls at all that way. And no code changes if a component needs to be moved to a different object.

    That being said... having 100 scripts on a single object means you're probably doing something else in a less than optimal way.
     
    CMDR_Garbage, Vryken and lordofduct like this.
  4. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,195
    The other thing to consider is what does ScriptA do? Is it a manager script and that is why all the other scripts need a reference to it? Does it just hold a bunch of data? Depending on it's purpose, you might just need a singleton or a manager script that can be called easily by other scripts without having to connect a reference.
     
    Vryken, seejayjames and PraetorBlue like this.
  5. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    Yeah this was also an idea I had but the issue stays more or less the same.
    This way I have to assign the same couple of components to those 100 scripts manually which is just tedious and that would make getcomponent alot faster.
    Or you could make a specific script that caches each component and then reference that "cachescript" in each other script :p

    100 scripts is exaggerated ofcourse it was just hypothetical but the reason I use 100 as an example is because I'm using a component based design which could cause a gameobject to have 20 components or so.

    So 20 components, 5 getcomponents in each script, for 20 gameobjects for example is is already 2000 getcomponent calls in a possible single frame.
     
  6. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    Ye exactly.
    For example playercontroller contains the player's state or something.
    Several components on that character need to know that state for some reason.

    Do that a couple more times for the audiosource, collider, health, etc... for several characters.
    It starts to add up.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    I gotta quibble there sir Praetor... GetComponent is indeed being called, it's just being done before you get called in Awake()/OnEnable(), that's all. :)

    I take your point that you don't have to maintain those GetComponent calls, but it just changes the boundary of who is responsible for doing the GetComponent... and we all know that if those GUIDs ever change, that component reference will be null just as sure as if you forgot to add GetComponent yourself.
     
    PraetorBlue likes this.
  8. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    This is why I'm asking, I do have an issue :p
    Aside from trying to speed up loading times I have a problem where my build freezes up when loading a scene async with addressables.
    Weird thing is the game loads perfectly when I launch for the first time but the times after it freezes and becomes unresponsive.

    I'm trying to troubleshoot it cause I have no idea why it freezes. I suspect something with memory or the scene/scripts being too "busy" hence my question.
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    Interesting. And I take it you've been unable to profile it at the moment it hurks on the second launch?? Are you seeing it on the built target or only in editor?

    It won't be an improvement to move something that you're doing too much of in Start() and instead do 60x per second more of it in Update()!

    Not sure how much RAM you're using but it could be some kind of garbage collection event... but really the only truth is the profiler, if you can wrestle it to give you that information. If you cannot, you sorta have to go with elimination of steps to find it, which is ...less than ideal.

    But I've had that happen before where profile-built development mode targets simply refused to run on the low-end hardware, which (of course) was the only place we had the problem.

    "Okay, remove audio." - Nope still problem

    "Okay, force all textures to 64x64 pixels" - Nope still there (this techique will quickly reduce your RAM footprint from textures, and it may get your profiler build running on hardware that it couldn't run on before.. handy!)

    etc.
     
    trombonaut likes this.
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,598
    I also do this approach.

    I also created an attribute that through my custom editor system it auto assigns the value from itself. I call the attribute 'DefaultFromSelf'.

    I like this approach of having it assigned through inspector because it visually says to me the dependencies a component has when I add it to the GameObject. This way if my designer is adding some new component and it's not working, he can just look at the inspector and see those "nothing"s in the inspector and resolve the issue on his own.
     
    PraetorBlue and Suddoha like this.
  11. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Not entirely sure whether the injection of serialized component references which is done by the engine is faster than caching by calling GetComponent in Awake or Start.
    One thing we all know though is that Awake/Start only runs on the main thread, whereas injecting the component references could potentially be done more efficiently behind the scenes.

    So you'd have to profile that and check the start-up times with a huge amount of components, once cache them within Awake, once assign them to serialized fields and compare the results.
    Afterwards, re-evaluate whether the number of components you need in your application are somewhat close to the number of components it requires to see a significant difference - if there's any at all.

    If the injection by Unity were faster, you could use OnValidate to auto-fill your serialized [component] fields, so that you neither need to drag&drop them into the slots, nor need to call GetComponent in Awake.

    *Edit Lord's attribute would be much cleaner, as you'd just need to annotate your fields and don't need to maintain OnValidate.
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    O^O

    This interests me Lord. Can you explain the flow of this, such as where it happens, when it happens (editor time, runtime), etc.?
     
    PraetorBlue likes this.
  13. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    It stops profiling the moment it's frozen so ye profiler is off no help.
    So indeed I'm just playing a guessing game.

    I doubt ram is the issue, I have 16 GB and it's not like I'm loading in a huge battle royale map with 100 players :p
    It's just a a "small village town" but it does have trees/bushes/water reflection/terrain/alot of small scenery like rocks, etc..
    So it's not empty either.
    All of this could also be the perpetrator ofcourse like static batching/occlusion/who knows.

    That's why I started this topic cause it's just one of my guesses I'm trying too eliminate.

    It's not so much that I'm gonna do everything in update I'm just wondering off the impact of calling too much code in the first couple of frames.
     
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,598
    So for starters it requires extending the editor system. How I do this is that I created a custom SPEditor class and I force hook it into the system.

    Here is my SPEditor class (note a lot is going on in here as my editor system does many things):
    https://github.com/lordofduct/space...er/SpacepuppyUnityFrameworkEditor/SPEditor.cs
    Note how I put in the 'CustomEditor(typeof(MonoBehaviour), true)', this actually forces Unity to use my editor for all MonoBehaviours that don't have editors assigned to it. This becomes my global hook into the editor system.

    Then I have special thing I call "PropertyModifiers", that are similar to PropertyDrawers, but instead of drawing the property... it only serves the job of modifying it.

    Here are a few of them my property modifiers, and the base class they inherit from:
    https://github.com/lordofduct/space...ster/SpacepuppyUnityFrameworkEditor/Modifiers

    Just like a PropertyDrawer you attribute the class as with what type it's targeting (attribute or type).
    upload_2020-8-18_17-58-15.png

    Now from how I hook into it from here gets really complicated. I have a this system that actually hooks into the Unity Editor system via reflection and other stuff to hack in my custom behaviours:
    https://github.com/lordofduct/space...aster/SpacepuppyUnityFrameworkEditor/Internal

    And in it I handle all the propertydrawers and filter them based on what they do. This allows me to have multiple property attributes instead of just one. Which is all done via my "MultiPropertyAttributePropertyHandler":
    https://github.com/lordofduct/space...rnal/MultiPropertyAttributePropertyHandler.cs

    BUT

    If we wanted to scale this back some since the 'DefaultFromSelf' behaviour doesn't actually need to hook into this system of mine. Instead in SPEditor we could snip out a lot of it.

    Still have the sealed 'OnInspectorGUI', have the 'OnSPInspectorGUI' (or whatever you wanna call it) that calls in the middle of 'OnInspectorGUI'. And then before calling 'OnSPInspectorGUI' you just loop through all the fields, pull attributes off of them, and filter out any that do things like DefaultFromSelf, and then perform what is necessary.

    In the case of DefaultFromSelf... this is the implementation:
    https://github.com/lordofduct/space...rkEditor/Modifiers/DefaultFromSelfModifier.cs

    Mind you again, a LOT is going on in here because my system supports a lot of tools. Like the 'VariantReference' that is referenced in there. But you can snip all that out and boil it down to the specifics which is the 'else' after testing for VariantReference.

    ...

    But having my multipropertyattributeblahblah allows me to accent my scripts without having to write custom editors all the time. Like so:
    upload_2020-8-18_18-9-39.png

    Can give me:
    upload_2020-8-18_18-11-14.png

    Which auto makes it use the reorderable array list drawer, it restricts what I can drop there to the interface to things that implement IOccupiedTrigger, and when I press play I can't interact with the property.
     
    Last edited: Aug 18, 2020
    PraetorBlue likes this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,598
    I'd argue the visuals is where I'd start first. If a scene is chugging, GetComponent is low on my list. Visuals/Assets are high as rendering is the biggest beast in the game by far always. I'd follow that with things like Physics, and that with things like if you have a lot of scripts active that all use 'Update' (even just having the Update empty in your script costs something).

    ...

    It sucks you're having issues with the profiler. Cause really that's the go to in this moment. What do you mean by "It stops profiling the moment it's frozen so ye profiler is off no help." ? If it's profiling up to that point I'd look for what is costing a lot leading up to being "frozen"... kind of like a derivative/limit in calculus... you maybe don't know what is at the undefined point in f(x), but you can calculate the values heading up to that point in f(x) to guess at what it is.
     
  16. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,947
    Are things completely freezing (and you need to alt+F4 out of Unity)? Or just slow? If it's completely freezing you may have an infinite loop in your code (or a much less likely deadlock).

    Slow startup code will only cause... well, a slow and hiccupy startup.
     
    Last edited: Aug 18, 2020
  17. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    Yanno, I'm sorry I misread your "build freezes up" line... you were very specific but because of the original post I mis-interpreted that as "it just pauses for a bit."

    As Praetor observed above, if you are truly crashing, it won't be from CPU performance. It is most likely an infinite loop somewhere.
     
  18. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    39,399
    @lordofduct Thanks for the thorough breakdown... appreciate you taking the time. I wish it was as simple as just implementing an attribute and calling it a day! :)
     
  19. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    692
    I don't know if the performance is different, but if everything needs a variable from Script A, definitely use a static class, which is accessible from any script in any scene (it's not attached to a GameObject). No need to call and/or cache any components, no need to worry about losing data between scenes, etc. That static class can also hold utility functions.
     
  20. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    Ye completely frozen, the exe becomes "not responding".
    I doubt it's an infinite loop cause it works perfectly in the editor, and even launches the first time after build.

    It's most certainly something with loadscene.async and something memory related.

    The exe becomes "not responding" during the scene load and the profiler stops recording the second it becomes unresponsive.
     
  21. lejean

    lejean

    Joined:
    Jul 4, 2013
    Posts:
    392
    K so I think I found the problem and the reason why my scene load stops responding is because vsync was disabled which seems to cause problems with the videocard running at 100%.