Search Unity

Feedback Is there such a thing as caching too much in MonoBehaviour Scripts?

Discussion in 'Scripting' started by iluvpancakes, Sep 12, 2020.

  1. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    My Scripts tend up to be filled to the brim with cached component references such as this:

    Code (CSharp):
    1. using Scripts.Combat;
    2. using Scripts.Core;
    3. using Scripts.Movement;
    4. using UnityEngine;
    5.  
    6. namespace Scripts.Control
    7. {
    8.     public class AIController : MonoBehaviour
    9.     {
    10.         [SerializeField] float chaseRadius = 5f;
    11.         [SerializeField] float suspicionTime = 3f;
    12.  
    13.         GameObject _player;
    14.         Fighter _fighter;
    15.         Health _health;
    16.         Mover _mover;
    17.         ActionScheduler _actionScheduler;
    18.  
    19.         Vector3 _guardPosition;
    20.         float _timeSinceLastSawPlayer = Mathf.Infinity;
    21.  
    22.         void Start()
    23.         {
    24.             AssignVariables();
    25.         }
    26.  
    27.         void AssignVariables()
    28.         {
    29.             _player = GameObject.FindWithTag("Player");
    30.             _fighter = GetComponent<Fighter>();
    31.             _health = GetComponent<Health>();
    32.             _mover = GetComponent<Mover>();
    33.             _actionScheduler = GetComponent<ActionScheduler>();
    34.            
    35.             _guardPosition = transform.position;
    36.         }
    To me this just feels cluttered and noisy. Is this really the most efficient way of going about the issue of not having GetComponent<>() calls in your Update() methods? And I don't mean to simply make them into [SerializeField].

    Thanks
     
  2. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    That's the price of caching the components. GetComponent is not exactly slow tho, so unless you have tons of scripts on one object, or hundreds of objects running quite a few of these commands every frame, i normally wouldnt bother. Still, it saves some computation time.
    If you are just annoyed by having some "wasted" space, you could put it into a defined region and collapse it:
    https://docs.microsoft.com/en-us/do...e/preprocessor-directives/preprocessor-region

    Here are some performance tests on GetComponent. There is probably a more up to date version but i doubt it became slower over the past couple years: https://snowhydra.wordpress.com/2015/06/01/unity-performance-testing-getcomponent-fields-tags/
    So if you dont have thousands of these per frame in total you should be fine.
     
  3. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    Thank you for taking the time and removing some of my performance worries. I am indeed nowhere close to numbers that would impact my game performance noticeably based on those tests.

    I also came across: https://learn.unity.com/tutorial/entity-component-system# Which to me seems like an amazing way to handle everything going forward, do you have any experience with ECS yourself?
     
  4. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    The overlying term to look for is DOTS, which is composed of ECS, Jobs and Burst. It's insanely fast. However it's also very different from the way you got used to write code (it's not even object oriented, but data oriented, tho they tried to keep as many OOP conveniences as possible). So it takes some good amount of time getting into it. The worst part about it is the documentation or lack thereof, and as all of it is still in development, it also still changes.

    So all in all, getting into it takes some serious dedication, frustration and most importantly time. The payoff is a speedup of most workloads by a factor of 10x-100x (that's times, not percent. No, really.).

    Is it worth it? Depends.
    If you know beforehand that you will have to take performance into consideration for your game design (commonly for RTS or simulation type games), it's certainly worth looking into. If you require absurd amounts of entities, in the region of multiple tens of thousands, it's worth looking into. If you have some expensive operations you want to outsource off the mainthread (for example procedural world generation, which is what i used it for), it can be worth looking into.
    Technically you can use DOTS to create all games in a more performant way. However, in reality the difference may not be noticable, while the time needed to get it working (including learning it in the first place) can be a huge investment, especially in its current state. Generally speaking i'd say DOTS excells at anything easily parallelizable, while losing a lot of its appeal when working on more complex, interconnected systems (which of course does not mean that's impossible either). You have to know for yourself if you want to get into it. Trying doesnt hurt, but then again, unless you know that you will need this amount of performance, i would not recommend it.

    As a rule of thumb, only start optimizing once you can profile an actual performance problem using the profiler. Otherwise you may spend time optimizing something that may never have become a problem, thus wasting time without any real life benefit to your game.

    Hope this helps giving you a rough overview.
     
  5. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    Yeah, I read through all the documentation I could find and I am not compelled in the least to start any new projects implementing ECS in the near future but I will definitely keep myself updated and try to dedicate a few hours per week fiddling around with it. I knew it would be a performance boost, but up to 100 times, that's insane..

    I actually find the data driven approach fairly intuitive, but I also see a compounding complexity factor doing simple things which makes it a bit of a no go for me until I decide to do something really meaty. At the moment I am working on a quite simple casual RPG where the performance overhead is really small comparatively.

    Thanks again @Yoreki , your replies are very insightful and helpful.
     
  6. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    From what I've seen, caching, object pooling. micro-optimization ... is pretty much poison here. Most successful games are fine with slop-tastic code. They're not even close to CPU-bound. For example Angry Birds uses a 3rd-party physics system. I'm sure that's optimized, but the code doing everything else doesn't need to be. Anyone with some coding background who's otherwise good at design, testing, finding art and so on can make a cute game. If you can convince them they need to optimize everything, their games will take twice as long to make, have more bugs buggier, and possibly be a tad slower.

    Take caching. It takes the programmer longer to set up. And if something is changed run-time (maybe a box collider is destroyed and replaced by a sphere sometimes), you have to remember to change the cached reference. Object pools take longer to set-up are no end of trouble as you re-use items that had color or size or something else changed that you forgot about and now need code to reset. Meanwhile, Instantiate works perfectly every time.

    And most beginners don't know how to optimize anyway. The major thing is cutting out extra loops. Double-looping for some list check can often be turned into a single loop if it's sorted. Removing from the front of a List<> is a loop. Every orc running a standard loop is a nested loop -- maybe it could be run once before-hand. If there's an actual problem, caching components is just screwing around, keeping you from seeing the real problems.
     
    Yoreki, bobisgod234 and Ryiah like this.
  7. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    It's not that your main statement is wrong @Owen-Reynolds, but..
    If we are talking about people with coding background that are good at design and testing, but their game ends up slower and buggier after optimizations, i seriously doubt their previously implied qualifications :D

    Sure, Instantiate works every time. And a game like Angry Birds probably wont need object pooling. But if we are talking about a game that constantly needs to Instantiate (and thus destroy) many objects, then the garbage collection can cause serious spikes in frametime. Those may not affect the FPS counter too much, but are definitely noticable and one of the most annoying things in otherwise seemingly well running games.
    I agree that some things like colors (shader) or some object states (scripts) can be easy to oversee when resetting. Tho resetting the transform should be pretty much the first thing one implements when returning an object from the pool.

    Edit: Because of the following discussion i wanted to clarify that "constantly" and "many" objects means literally that. You wont need object pooling if you only have a hand full of enemies that die every couple seconds. However, if you have a projectile based minigun weapon, or kill douzens of monsters per frame (ARPG-style), then you probably want to consider it.

    Very much true!
     
    Last edited: Sep 16, 2020
  8. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    I'm talking about people in a good spot to ask programming advice here: they know enough to ask a reasonable question, but still at a level answerable by the regulars. People who could go on to make games with some help. My beef is that I don't get to play the games they don't make when advice here spirals them off into efficient-code land.

    Argg! Stop! Now someone making a game with 20 monsters total who die at most every few seconds will fly into a panic and attempt to turn them into an object pool; which creates enough delay and frustration so they never finish the game.
     
  9. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    When I have a GameObject with lots of scripts that need references to each other, I have started to make a single script which references all the other scripts on the same GameObject. Then all the other scripts just need a reference to the one script with all the references. Cuts down on GetComponent calls and reference variables in the scripts you're actually working on regularly.
     
  10. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    And i was talking about the hypothetical people you mentioned with a coding background that were good at testing and designing, and yet cause their game to be buggier and slower when trying to optimize it. I questioned these hypothetical peoples' hypothetical qualifications if that's the case. It was a joke. I even put a laughing smiley there, not sure what else i could have done :D
    A person with coding background that is good at testing and design should not ruin their game when trying to do optimizations. If they do, they simply do not have the aforementioned qualifications.

    Of course that's something we wouldnt want. Which is why they should profile for performance problems before jumping the gun and implementing random optimizations. Which is also why i mentioned exactly that in my very first reply concerned with optimizations:
    I'm honestly not sure why this is becoming such a big deal. If some beginner literally skips all i wrote above, starts reading a discussion somewhere in the middle and just reads the one part of that one reply i wrote to you, in which i state that under the very specific circumstance that there are many instantiations and destructions of objects, a certain optimization called object pooling can be useful, and this person just so happens to lack the knowledge to understand what "many" means, does not care to profile for a performance problem first, and instead just implements optimizations they dont understand.. i simply do not know what i could have done differently. I did my job when i mentioned that normally one only optimizes after profiling an actual problem.
    If you disagree on that, we can agree to disagree, but we are moving away from relevant informations regarding this thread.
     
    Last edited: Sep 15, 2020
    Joe-Censored likes this.
  11. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    You can't cache the position. Vector3's are structs, which are value-types. _guardPosition is just an x,y,z variable that starts with the starting position, but has no more link to it that
    int A=B;
    links A to B.

    Often it's nice to have this as a master variable. You can easily change _guardPosition then once, at the end, copy it into position. But it's a different thing than saving a reference.
     
  12. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    My suggestions for how not to side-track users with non-helpful optimization advice:

    o If a Q isn't about optimization, resist the urge to jump in with it. Even if someone is using GameObject.Find they can easily fix it later, if they even need to.

    o If someone else hijacks a Q into optimization, resist the urge to keep it going. If you're comfortable with it, reply that using a few Finds is fine, that guy just has a complex about it since a Find command stole his dog.

    o When someone who may not need it asks about optimizing, suggest they finish the game and optimize later if needed (this seems unfair, since I just said "no hijacking", but 10,000 normal Q's have been hijacked by optimization. We have to balance by hijacking the other way 10,000 times).

    o For a Q about one particular way to optimize, avoid going off into every other optimization topic. More than likely that's making a confused person even more confused and stalled out.

    o People give weight to what there's more of. A short "you probably won' t need X" followed by a paragraph of how to do X is an endorsement. It's a bit like saying you should almost never marry your dog, but if want to, here's the detailed plan.

    o Running the Profiler is part of optimizing. There's no point suggesting it to someone without a speed problem.

    o Avoid ominous vague warnings.about the perils of not optimizing. 5 minutes of play-testing is the whole story. If there are no speed problems then, there are no speed problems. There are a few things which could possibly crash a game on;y after an hour, but they will be so specific that the usual advice wouldn't prevent them anyway (and there are successful games that did that. People gave 5 stars and wrote "plz fix crashes").
     
    Joe-Censored likes this.
  13. Yoreki

    Yoreki

    Joined:
    Apr 10, 2019
    Posts:
    2,605
    @Owen-Reynolds i get what you are saying and i agree, but i disagree with me violating even a single point:
    • OP asked about ECS, which is part of DOTS. In order to tell him why i dont think it's a good idea for him to get into it some pros and cons were more than justified.
    • The one that hijacked the thread with a different topic is you. Sure, you were in the "dont do optimizations" camp, but at that point all was said and done. OP was informed about the pros and cons of the topic he asked about, and already received a warning that he should not optimize unless there is an actual problem.
    • Exactly what i did when i told OP not to optimize until there is an actual problem.
    • I replied to what was asked directly by OP. You then brought up unrelated topics like object pooling. I then replied to your statement since it made object pooling seem completely useless and inferior to just instantiating objects, which is objectively speaking wrong and i wanted to correct that. Sadly we let things escalate a bit from there.
    • I'm not aware of going against this suggestion. At no point did i say "dont do X, but here is how you'd do it". If anything i gave an overview of pros and cons, as well as examples for the areas of game development this may be useful for. I then told OP that it's probably not for him. Even if you think otherwise, OP took it exactly like that, which is also reflected in his statement that he is not compelled to start such a project anytime soon.
    • The profiler is a tool for profiling. Sure, you profile before optimizing, but you can also use it to see that you do not need to optimize. Also, telling someone they "shouldnt optimize unless they can spot an actual problem using the profiler" is not exactly suggesting someone to go and use the profiler right now.
    • Again, i never did this. I agree that people should not optimize before having problems, but then again.. that's literally something i already said in my very first post involving any optimization related topics. So it's not like we disagree here. It's just something that wasnt done in this thread.
    Really, i just dont see any problem with what i wrote, or how i wrote it. I'm just embarrassed by what became of this thread, but i dont see any reason for why i should just take being blamed for things i did not do. Anyways @Owen-Reynolds , we both presented our views on this topic. Multiple times. If you feel the need to go into this further, please write me a direct message. We can then discuss this in private without annoying half the community or further hijacking this thread off track.

    @iluvpancakes I'm very sorry for all of this. Normally that's not something that happens to a topic. Please dont be afraid to ask further questions. I think i'm talking for the both of us when i say that we will try to make sure this does not happen again in the future!
     
    Last edited: Sep 16, 2020
    Joe-Censored likes this.
  14. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    Yes it is. All the time. Forums allows the thread to quickly diverge from the original Q. Most do, and that's fine. But it's too common to have them immediately diverge into "oh, you should cache that". Someone new sees all those threads and takes it seriously. Not on purpose, but this forum is full is terrible advice for new programmers. This poor guy wrote a dozen lines of code that do nothing useful and has a tricky error to boot.
     
  15. iluvpancakes

    iluvpancakes

    Joined:
    Jul 19, 2020
    Posts:
    18
    @Yoreki I could sense that the derailment was not something you enjoyed and I understand why, but truth be told I learned a few things listening in on you two and the result of it was definitely a net positive for me and my learning experience. Imo you're a class act and would love to have input from you in the future.

    @Owen-Reynolds I understand where you were coming from but as @Yoreki pointed out you might have not picked the best case since, as far as I can tell, he did not try and push me into optimizing my game, he simply replied to my question. I would rather say that my question was probably way out of scope of any question I NEEDED to ask at this point in my development life-cycle, but it was a question asked out of curiosity and sometimes people do not feel hindered or sidetracked by learning things outside the scope of their current project or knowledge level.

    I appreciate the time and effort both of you put into this thread and I wish you both the best.
     
  16. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,106
    Without having to delve any deeper into this, I will nod in the general direction of how things should be done.

    You need a chair?
    Ok, you can do this and this and that and that. Some duct tape. Done.
    That's coding.

    Ah, you want the chair to not break when you sit on it? Ah well why didn't you say so. Well let's see....
    That's optimization.

    You need a chair? Well, if we factor in the amount of Moon's gravity, and the net effect of tidal forces...
    That's over-optimization. Who even does this?

    In any case, pick whatever approach suits you the best.

    In general don't do it if it's over your head and if you don't care that much about the tiniest of the considerations. Many parts of many games can do without any optimization step, and without even basic logistics in place. Like everything's just ad hoc slapped and if it works, it works. Sure, you'll end up wasting a mobile battery, but if it's a PC game, meh, there are many more much more serious culprits out there. So if you're a beginner, don't be afraid to make anything, even if turns out to be a mistake. Who will care in this huge world? You'll definitely learn from it.

    But after a while, you'll have enough experience to subjectively engage in such conversations about meaning of life, universe, and optimizations.

    In the end, just don't make s*** for your own sake.
    Pardon my language, but that's all there is to it.
    It's a great piece of discussion, even if it got derailed.

    (Though tbh in my age, it's getting rather stale. It's always the same, for decades now.)
     
    Joe-Censored likes this.
  17. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    I thought what _you_ wrote in your top post was a good case. I'm guessing you read several posts in Forums talking about how important caching components was. After the 10th one you figured 10 people can't al be wrong and started doing that in your code. Then you realized what a pain it was to write that way ("noisy and cluttered"). Then you got wishy-washy advice here: "you don't need to do it, but here's more ways to do it".

    I'm saying that 10 people are wrong all the time on the internet, and we should do better. This poor guy (you) wasted time on something useless and it's our fault and we should stop. Also, to be sure: transform.position can't be cached. Position is a Vector3, which is a struct, which is a value-type. _guardPosition = transform.position; is making a copy.
     
  18. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,106
    true, true, caching Vector3 is absolutely meaningless, and shows a clear sign of a tendency to over-optimize because of unfounded fears of underperformance. this in turn leads to misconceptions, misplaced attention, bad mouth on forums, and poor product performance or maintainability.

    OP, you need to learn more. don't just jump to any conclusion you find on the internet. coding IS sensitive, but not that sensitive that you need to needlessly juggle and defensively copy every little bit of information for superstitious fears. frankly, you should always double-check reasons for any such superstition.

    I still have to check and recheck every little assumption I make after 30 years of programming. we all have our blindspots. base your engineering decisions on benchmarks when possible. I hate to over-optimize or to generally apply magical cure-all snake oils, because it's commonly just as bad as falling into a trap of doing it easy and badly. if you adopt this as an intuition it becomes easier to guess where else you might be mistaken.
     
    Yoreki likes this.
  19. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    Way, way back I was taught that
    i=0; while(i<len) A[i++]=whatever
    was best, since A[i++] mapped to a single "use as index and increment" instruction in most assembly code. I did it for a few years and then realized it was probably no longer the case and I wasn't writing an operating system anyway.
     
  20. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,106
    lol, this is why I forget things so easily. don't get me started on "how things used to work" ... even if I tried, I wouldn't remember any of it. the only thing I remember nowadays is that I used to remember *everything* in my 20's, like a true master. well, what a waste of memory that turned out to be :)

    still I tend to ponder over the actual usage of any code I write. I will at least try to hyper-optimize anything sufficiently small and reusable that is bound to end up on a hot path. branchless seems to be hit and I like using that constraint as an added challenge. it's tricky! almost like a little puzzle game in itself.

    but then you end up with code that looks like this
    Code (csharp):
    1. var z = (qlot.x & 0x3);
    2. var b = ((qlot.y & 1) ^ _boundaryTest[z]); // has cell boundaries
    3. var w = z & 1; // boundaries are flipped
    4.  
    5. w = (w << 1) - 1; // stretching hack
    6. var hotPoint = MapConstants.TILE_SCALE * new Vector2(grid_f.x + (.5f + EPS * w), (grid_f.y - EPS) * MapConstants.HEX_OBLIQUITY);
    that's sufficiently low level for my taste, and this successfully replaced a switch block and half a dozen of ifs, but then you really need to have BOTH proto code, and a permanent visual diagram of what's going on. I am not joking. sometimes I draw stuff in ascii to have it in code as well.

    this method for example turns vector2(x,y) to vector2(y,x) conditionally without ifs
    normally I wouldn't do this at all, but the path this is on will likely have hundreds of such calls per frame
    I'd really like it to be CPU cached like there's no tomorrow
    Code (csharp):
    1. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    2. static public Vector2 Transpose(this Vector2 vec2, int flag)
    3.   => new Vector2((flag ^ 1) * vec2.x + flag * vec2.y, (flag ^ 1) * vec2.y + flag * vec2.x);
    getting this dirty has its merits, but I do not recommend what is shown here, until you do actually have 30 years under your belt ... and when you don't forget about things ... oh man