Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Doubts about abuse of "out" parameter

Discussion in 'Scripting' started by asdasdasdXX, Aug 3, 2020.

  1. asdasdasdXX

    asdasdasdXX

    Joined:
    Jun 1, 2020
    Posts:
    15
    After a long journey I realized I can make my code 20 times more readable thanks out parameter which I convieniently never knew about. However I several questions regarding feasibility and performance, take following pseudo code as example:
    Code (CSharp):
    1. public static class Mention
    2.     {
    3.  
    4.         class Item
    5.         {
    6.  
    7.             public string gameObjectName;
    8.             public GameObject gameObject;
    9.             public MyComponent myComponent;
    10.  
    11.         }
    12.  
    13.         static Dictionary<int, Item> test = new Dictionary<int, Item>();
    14.  
    15.  
    16.  
    17.  
    18.         public static void OldMethod(int targetId, GameObject original)
    19.         {
    20.             GameObject newgo = GameObject.Instantiate(original) ?? throw new Exception("Spawn failed");
    21.             MyComponent mc = newgo.GetComponent<MyComponent>() ?? throw new Exception("Not found component");
    22.  
    23.             try
    24.             {
    25.                 test[targetId] = new Item
    26.                 {
    27.                     gameObject = newgo,
    28.                     myComponent = mc,
    29.                     gameObjectName = newgo.name
    30.                 };
    31.             }
    32.             catch (Exception e)
    33.             {
    34.                 throw new Exception("Failed");
    35.             }
    36.         }
    37.  
    38.  
    39.         public static void NewMethod(int targetId, GameObject original)
    40.         {
    41.             GameObject newgo = GameObject.Instantiate(original);
    42.  
    43.             if (newgo.TryGetComponent(out MyComponent mc))
    44.             {
    45.                 if (test.TryGetValue(targetId, out Item targetItem))
    46.                 {
    47.                     targetItem = new Item
    48.                     {
    49.                         gameObject = newgo,
    50.                         myComponent = mc,
    51.                         gameObjectName = newgo.name
    52.                     };
    53.                 }
    54.                 else
    55.                 {
    56.                     throw new Exception("Failed");
    57.                 }
    58.             }
    59.             else
    60.             {
    61.                 throw new Exception("Not found component");
    62.             }
    63.         }
    64.     }
    Either way I will be forced to use out parameters out of pure necessity, just like System.Linq, where utility and readability are outweighing the performance cost. But I was wondering whether it was performant of me to use out parameters in places where they might not entirely be necessary. So I'm left with couple questions which aren't exclusive to out. My issue isn't readability in general public. I intent on "abusing" out in reusable code

    Is there a performance impact for any return value that isn't assigned? If I return a massive struct/class/ValueTuple, but that method doesn't assign it to any variable, is there something that I should be doing differently? Does that cost?

    Is there any performance gain on operating on the "out" output exclusively without assigning it to anything as opposed to getting the output regular way into a variable and executing operations on that variable (assuming its a class)?

    What would you say is the more appropriate way to obtain Item in these scenarios:
    Code (CSharp):
    1. public static void x()
    2.         {
    3.             Item item = itemList[777] ?? throw new Exception();
    4.             // vs.
    5.             if (!itemList.TryGetValue(777, out Item item)) throw new Exception();
    6.  
    7.  
    8.             MyComponent mc = gameObject.GetComponent<MyComponent>() ?? throw new Exception();
    9.             // vs.
    10.             if (!gameObject.TryGetComponent(out MyComponent mc)) throw new Exception();
    11.  
    12.  
    13.             bool retItem = itemList.TryGetValue(777, out Item item);
    14.             // vs.
    15.             (bool, Item) retItem = itemList[777];
    16.  
    17.         }
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    3,300
    The cost is in the construction and allocation of memory for the return value. For the actual "return" part, the cost is just the cost of copying a single reference (for reference types) or copying the entire struct (for value types). That cost is not large. Whether or not you assign it to any variable in the calling code doesn't make any difference as far as I know.

    If you're talking about assigning to a local variable vs declaring a variable inline with the out parameter, there is no difference. E.g. the following two are exactly the same performance-wise and most likely compile to the same result:
    Code (CSharp):
    1. gameObject.TryGetComponent<MyComponent>(out MyComponent mc);
    2. // vs
    3. MyComponent mc;
    4. gameObject.TryGetComponent<MyComponent>(out mc);
    Individual cases:

    Code (CSharp):
    1.             Item item = itemList[777] ?? throw new Exception();
    2.             // vs.
    3.             if (!itemList.TryGetValue(777, out Item item)) throw new Exception();
    If you're going to throw an exception if the itemList doesn't exist, the top one probably makes more sense I guess. Although these bits of code actually do different things. The top will already throw an exception if the index is out of range, and it will only be null if there's an actual explicit null entry in the list. The bottom one tells you if there was a thing at that index or not. From a performance perspective they are pretty much identical. The point of TryGetValue type functions is generally to avoid the use of exceptions for program control though.
    Code (CSharp):
    1.             MyComponent mc = gameObject.GetComponent<MyComponent>() ?? throw new Exception();
    2.             // vs.
    3.             if (!gameObject.TryGetComponent(out MyComponent mc)) throw new Exception();
    Again, same as the previous.

    Code (CSharp):
    1.             bool retItem = itemList.TryGetValue(777, out Item item);
    2.             // vs.
    3.             (bool, Item) retItem = itemList[777];
    These two bits of code do different things. The top one looks like a List<Item>. The bottom one assumes a list of tuples: List<(bool, Item)>.

    Additionally, the TryGetValue version is generally for when you're not certain that the element will exist. The other version is for when you are certain it will exist. Performance-wise they will be extremely similar. Not different in any noticeable way.
     
    asdasdasdXX likes this.
  3. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    2,455
    I don't think "out" has any performance impact worth mentioning.
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    7,340
    I'm going to start by saying... the performance difference between out and not out is pretty much none. Sure they technically allocate differently (an out actually requires a large stack frame than a return, but it makes up for it by utilizing a memory pointer for copying), they end up being reallllllllly similar that you won't see any real world difference in the 'out' itself. You may see performance difference based on the code you've written around it to meet the requirements of your chosen pattern.

    Well some performance considerations to make off the bat is if you write custom 'out' versions of methods that just call another method and make a check on it will be slower if only because you're turning a single call into 2 calls.

    But using existing 'Try->out' methods, or writing those that don't just wrap existing methods, there aren't many performance differences that you'd have to really consider. Actually sometimes it could actually be faster.

    Well... for starters 'out' parameters require that the parameter is set. You can't not set it.

    Take for instance this method:
    Code (csharp):
    1.  
    2. public static void Foo(out int value)
    3. {
    4.     Debug.Log("Foo");
    5. }
    6.  
    This will throw a compiler error since 'value' was never set. "ref" on the other hand allows you to not have to set the parameter. Basically the difference between out and ref is that:
    ref - allows passing in a variable reference that can be read/write
    out - allows passing in a variable reference that must be set (can not be read until it's set)

    So not setting it is kind of moot.

    Now if you mean not setting it before calling the 'Try->out' method... oh that doesn't matter. You don't have to set the variable, the out does that for you. It's actually less performant to set it. For example... lets say you had this:

    Code (csharp):
    1.  
    2. Vector3 v;
    3. SomeMethod(out v);
    4.  
    vs
    Code (csharp):
    1.  
    2. int v = new Vector3(1,1,1);
    3. SomeMethod(out v);
    4.  
    Technically the second is slower because you're doing extra work. You've called a Vector3 constructor with values. And since the 'out' overwrites 'v', there was no point in even doing it. The first option is the preferred option here.

    I'm not sure what you mean... are you saying the difference between these?

    Code (csharp):
    1.  
    2. MyComponent c;
    3. if(gameObject.TryGetComponent(out c))
    4. {
    5.     c.Foo();
    6. }
    7.  
    VS
    Code (csharp):
    1.  
    2. MyComponent c;
    3. if(gameObject.TryGetComponent(out c))
    4. {
    5.     var obj = c;
    6.     obj.Foo();
    7. }
    8.  
    If you are... the second is technically slower because you're doing more work and allocating more variables on the stack.

    If you mean something else... like:
    Code (csharp):
    1.  
    2. MyComponent c;
    3. if(gameObject.TryGetComponent(out c))
    4. {
    5.     c.Foo();
    6. }
    7.  
    VS
    Code (csharp):
    1.  
    2. var c = gameObject.GetComponent<MyComponent>();
    3. if(c != null)
    4. {
    5.     c.Foo();
    6. }
    7.  
    In this situation... that really depends on how TryGetComponent and GetComponent are implemented.

    The actual variable allocations are the same on the stack. And the actual implications of stack allocations vs the 2 methods are negligeable vs each other (out has a larger stack frame, but uses a memory pointer which counters the smaller stack frame of the none out).

    But the underlying code will have a huge outcome on things. How does TryGetComponent determine returning true or false?

    It just happens that TryGetComponent uses the same != null operation inside itself. So these specifically work out to be roughly the same in speed.
    https://github.com/Unity-Technologi.../Export/Scripting/GameObject.bindings.cs#L178

    But that's incidental. And can vary from method to method.

    More appropriate? That's subjective.

    For the first one... well the 2 behave vastly differently. "itemList[777]" has the potential to throw a IndexOutOfRangeException, where as the "itemList.TryGetValue" will not. So technically they're not even the same thing. (consider if you wrapped the method 'x' in a try/catch and only caught for IndexOutOfRangeException, you'd get very different behaviour from both implementations)

    I'm going to come back to the 2nd since there's a LOT to discuss on that one.

    In the case of the 3rd, we're back to the first one in that they don't have the same behaviour since [777] can throw an indexoutofrangeexception. Furthermore in this example you seem to imply that itemList is a tuple in the 2nd which further alters the behaviour. The list is no longer the same kind of list.

    ...

    Now back to that middle one.

    Now... at first glance these would appear to behave similarly. If we pretended that GetComponent didn't return an object that was a UnityEngine.Object, but rather a normal C# object, then these 2 would be pretty much the same thing. The only difference would be the generated IL underneath, and the "?? throw" technically has more efficient IL since it doesn't stloc the 'mc' variable until after running a brtrue.s instruction on the top of the stack for the returned object. Where as the TryGetComponent technically would be more due to how it has to assign obj before the branch.

    But I mean... this performance gain is only applies when an exception was thrown. So not only is it teeny weeny... it also implies your application has entered an unwanted state. So there's that to consider.

    BUT, there is another implication to consider.

    Unity overrides the == and != operators for all UnityENgine.Objects (this includes Components). Unity has a special override that evaluates as null even if it's not null, but it has been destroyed.

    Problem is the ?? does not respect this override of the == and != operators.

    So if these were normal C# objects, the 2 would be the same.

    But since they're not, and actually have this weird behaviour to them... these 2 aren't actually the same behaviour wise.

    ...

    So which is more appropriate?

    Well, since none of the 3 examples behave the same as each other. The appropriate choice depends on what behaviour you actually want/need.

    ...

    TLDR;

    Performance isn't my concern here.

    What is though is understanding the implications of the patterns you choose to use since all your examples behave differently depending on your pattern chosen.
     
  5. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    8,953
    Might be interesting if someone did "out" vs a regular return value a million times and see if there is any significant time difference.
     
  6. asdasdasdXX

    asdasdasdXX

    Joined:
    Jun 1, 2020
    Posts:
    15
    All fantastic and very informative answers, thank you guys.
     
unityunity