Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Using hash

Discussion in 'Animation' started by NestorAlgieri, Sep 18, 2018.

  1. NestorAlgieri

    NestorAlgieri

    Joined:
    Sep 11, 2011
    Posts:
    299
    Hi! The documentation says it's more efficient to use hashNames when dealing with the animator.
    I'm assuming the costly part us when calling animator.CrossFace(), SetTrigger(), etc.
    But what if I call animator.SetTrigger( Animator.StringToHash( SOME_PREFIX+someDynamicSuffix) );
    That would avoid the costly call of setTrigger with a string, but is the StringToHash() call costly as well?

    Thanks.
     
  2. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,862
    If you call animator.SetTrigger( string ), it will basically be doing an internal call to StringToHash() for you. If you were to keep a Dictionary of a whole bunch of different strings, then C# would basically be doing an internal call to StringToHash() just to do the lookup. These APIs which accept a hash are useful if you have a few predefined strings and you can cache their hash numbers ahead of time. This kind of performance is really pretty small, so if you're not doing it millions of times, you probably won't see much impact. (This would be less impact than, say, caching your GetComponent<> lookups.)
     
    NestorAlgieri likes this.
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    It's a micro-optimization, but it also isn't hard to use in any way, so it's probably a gain.

    I've set up a file that just caches all Animator hashes used in the game, which looks like:

    Code (csharp):
    1. public static class AnimatorNames {
    2.     public static int Attack = Animator.StringToHash("Attack");
    3.     public static int Idle   = Animator.StringToHash("Idle");
    4.     //etc
    5. }
    Which means that instead of doing
    myAnimator.SetTrigger("Attack");
    , I do
    myAnimator.SetTrigger(AnimatorNames.Attack);
    .

    This has the added benefit of having a centralized place where all strings used are listed, meaning that if you're looking at an AnimatorController and are wondering where it's triggers and bools are used, you have a single place to check.[/Code]
     
  4. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    The primary goal was to avoid string manipulation in an update function because it can allocate manage memory which then trigger the GC.
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    Is the hashes stable over versions?

    That would mean that I could calculate the hash values, write them down, and then use them directly? So my example script would be:

    Code (csharp):
    1. public static class AnimatorHashes {
    2.     public const int Attack = 1080829965; // result of StringToHash("Attack");
    3.     public const int Idle   = 2081823275; // result of StringToHash("Idle");
    4.     //etc
    5. }
    It's a micro-optimization for sure, but it's essentially free cpu cycles since the code using it looks the same.
     
    Bloodberet likes this.
  6. NestorAlgieri

    NestorAlgieri

    Joined:
    Sep 11, 2011
    Posts:
    299
    So if it's not in an update function it's not a big deal I guess?
     
  7. Mecanim-Dev

    Mecanim-Dev

    Unity Technologies

    Joined:
    Nov 26, 2012
    Posts:
    1,675
    yes we do use the same hash algo since 4.0.
    it need to be stable because you can have an asset store plugin with pre build assembly that use it.

    it up to you to validate if it a big deal or not, some game want to avoid at all cost any GC collect because their game is already running on the limit of available memory or CPU budget.

    I would says that it always a good pratice to try to get the maximum performance from your game but depending on what is the end goal it's up to you to make the final call if it worthy or not. We have tools like the profiler to help you make the call.
     
    NestorAlgieri likes this.
  8. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,485
    My Weaver plugin generates a procedural script like your class for all AnimatorController states and parameters in the project and I've never had a problem caused by one of the hashes changing. It also generates a function to convert a hash back into its string and it's never tripped the hash collision check either. So it seems like they chose quite a good algorithm.
     
  9. alti

    alti

    Joined:
    Jan 8, 2014
    Posts:
    94
    Does this kinda thing only matter with "setbool" and "setint" type of stuff?

    What about when you use GetComponent<Animator>().Play("animName");
    Is it still more performant to use the hash instead of the name in that instance? Does it accept it like anim.Play(1234);?

    I guess I could check, but I figured I'd ask and get a straight answer in case anybody else stumbles on this thread. The profiler tells me there are some moments of GC, but idk how to read it to tie it to this kind of thing, and so far even the documentation is only mentioning setting "parameters" instead of playing actual clip "names".
     
  10. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,485
    This all applies to Play and CrossFade just the same as with parameters.

    The GC you're seeing is likely something else. None of this should allocate any garbage.
     
  11. alti

    alti

    Joined:
    Jan 8, 2014
    Posts:
    94
    Ok. In the meantime I figured out you can play an anim using using these values:

    print(_asi.shortNameHash + " | " + _asi.nameHash + " | " + _asi.fullPathHash);
    _asi is an animatorstateinfo variable. NameHash and fullpathhash are apparently the same thing, the former deprecated.

    Sometimes those two (namehash and fullpathhash) would be the same value and other times they were different. odd--but all printed values played the same animation anyway.

    Collecting these values to play instead of using the state name is gonna be its own project. The only other thing I'm curious about is if there's any benefit to doing:

    Animator anim = GetComponent<Animator>();
    anim.Play("Run");
    vs
    anim.Play(Animator.StringToHash("Run"));

    Or does it save on performance only when already cached as an int?

    I assume the two are calculated by unity ~the same, but it never hurts to ask. Also, thanks for such a quick response. I've got over 300 animations in my AnimatorController, so collecting these and pairing them with hashes would be a real chore. Up til now I never considered if checking 300+ strings is a secret killer every time I call the animator to play something via a string. Thanks for any insight or scripts anybody can provide to benchmark this kinda thing.
     
  12. alti

    alti

    Joined:
    Jan 8, 2014
    Posts:
    94
    I tried this:


    Code (CSharp):
    1.  
    2. float previousTime = Time.time;
    3.         yield return null;
    4.  
    5. for(int i = 0; i < 100000; i++){
    6.             anim.Play(701504055);
    7.             // print("tt " + i);
    8.         }
    9.         print("1 tt: " + (Time.time - previousTime));
    10.         yield return new WaitForSeconds(1f);
    11.         previousTime = Time.time;
    12.         yield return null;
    13.         for(int ii = 0; ii < 100000; ii++){
    14.             anim.Play("s0");
    15.             // print("tt " + ii);
    16.         }
    17.         print("2 tt: " + (Time.time - previousTime));
    but even through the profiler I saw no difference. I even added the yield line to make sure the initial call to do the anims would start processing on the next frame, unaffected by anything before/after. The time.time - previoustime result was always very similar (0.01), and even the profiler said this:



    Identical resources required using an int vs a string. Unless if I'm doing something very fundamentally wrong here, it doesn't seem worth it to go about replacing animator strings with animator hash ints for the sake of performance.
     
  13. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,485
    Play("String") just calls StringToHash internally so it would only matter if you cache the int.

    Repeated calls to Play on one object might have a different cost compared to calling it on different objects and actually changing the state each time.

    I've never tested the performance though, so it still might not be significant. My main problem with the system isn't performance, it's the bad code you end up with due to needing magic strings all over the place. Caching the hashes in one place makes it a bit better, but it's still not a great system either way.