Search Unity

Does the Inheritance lower the performance ?

Discussion in 'Scripting' started by U7Games, Oct 21, 2018.

  1. U7Games

    U7Games

    Joined:
    May 21, 2011
    Posts:
    943
    I don´t really need to split my class into subclasses, but I think this will help me a bit more for better organization, but not sure if performance will be affected, I´m insanely careful with performance...

    Does the Inheritance lower the performance?.

    Thanks.
     
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Technically & in general: yes, but there are cases in which the lookup in the vtables aren't necessary. There's lot's of information out there in the internet.

    In practice, that shouldn't be a problem unless you call tons of virtual methods per frame, or even in tight loops.

    A great alternative to inheritance is composition. You may take a look at it, as it also comes with various other benefits.
     
    lordofduct and U7Games like this.
  3. U7Games

    U7Games

    Joined:
    May 21, 2011
    Posts:
    943
    Thanks I will check how to manage composition according to what I need..
    Greetings...
     
  4. newjerseyrunner

    newjerseyrunner

    Joined:
    Jul 20, 2017
    Posts:
    966
    The difference is that non inhetirtwd method calls can be directly addressed by the compiler and can make direct memory address calls. Inheritance was a list of pointers for each method which has to be access at runtime. The performance hit is essentially zero. The difference is a single ram access, which will likely be in the L1 cache anyway.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    I'm not following this statement...

    This seems to be saying that if I have a ClassA and a ClassB which inherits from ClassA, but call a method defined on ClassA from ClassB that the compiler can't directly address it. Like so:

    Code (csharp):
    1.  
    2. public class ClassA
    3. {
    4.      public void Foo();
    5. }
    6.  
    7. public class ClassB : ClassA
    8. {
    9. }
    10.  
    11. ClassB obj = new ClassB();
    12. obj.Foo();
    13.  
    But that's not so. The compiler can clearly determine where 'Foo' is located. It a sealed method on ClassA. The compiler obviously knows where it's at. Even if 'obj' was typed as 'ClassA' it would still be fine since 'Foo' is sealed. There's no way there's another version of Foo out there, the compiler knows this is it.

    Where inheritance has an issue is the inverse of this.

    Lets say ClassA doesn't seal the method, and instead marks it virtual (like @Suddoha talks about), and you polymorphically reference ClassB as a ClassA. You get:

    Code (csharp):
    1.  
    2. public class ClassA
    3. {
    4.      public virtual void Foo();
    5. }
    6.  
    7. public class ClassB : ClassA
    8. {
    9.      //may or may not override Foo
    10. }
    11.  
    12. ClassA obj = new ClassB();
    13. obj.Foo();
    14.  
    Now the compiler isn't quite sure if more than 1 version of Foo exists, and which one to specifically call. So now it has to look up specifically which type 'obj' is from the type object pointer associated with the object. Then from this it checks the vtable for which version of Foo to call.

    This is all performed via the 'call' and 'callvirt' IL commands. Where 'callvirt' is used when the compiler can't directly suss out which method it is and says it'll need to be figured out dynamically/virtually at runtime.

    The problem here isn't the inheritance. It's the virtual nature of the Foo method.

    ...

    Note that if we sealed 'Foo' in ClassB, and we referenced it as ClassB or any subset of ClassB, we again lose this issue. Like the following:

    Code (csharp):
    1.  
    2. public class ClassA
    3. {
    4.      public virtual void Foo();
    5. }
    6.  
    7. public class ClassB : ClassA
    8. {
    9.      public sealed override void Foo();
    10. }
    11.  
    12. public class ClassC : ClassB
    13. {
    14. }
    15.  
    16. ClassC objAsC = new ClassC();
    17. ClassB objAsB = objAsC;
    18. ClassA objAsA = objAsC;
    19.  
    20. objAsA.Foo(); //callvirt
    21. objAsB.Foo(); //call
    22. objAsC.Foo(); //call
    23.  
    When accessing the object as objAsA, it's a virtual call, because the method Foo is virutal on ClassA.

    But accessing the object as objAsB, it's a sealed method, because the Foo method has been sealed at this point. So the compiler knows exactly where to look.

    Same goes for ClassC since it inherits the sealed nature of Foo.

    ...

    Note that this pertains to the 'new' decorator as well, and why it behaves the way it does. If ClassC implements a 'new Foo' method, it can override the implementation. BUT it only works if you access it as a ClassC:

    Code (csharp):
    1.  
    2. public class ClassA
    3. {
    4.      public virtual void Foo()
    5.      {
    6.           Debug.Log("I'm an A");
    7.      }
    8. }
    9.  
    10. public class ClassB : ClassA
    11. {
    12.      public sealed override void Foo()
    13.      {
    14.           Debug.Log("I'm a B");
    15.      }
    16. }
    17.  
    18. public class ClassC : ClassB
    19. {
    20.  
    21.      public new void Foo()
    22.      {
    23.           Debug.Log("I'm a C");
    24.      }
    25.  
    26. }
    27.  
    28. ClassC objAsC = new ClassC();
    29. ClassB objAsB = objAsC;
    30. ClassA objAsA = objAsC;
    31.  
    32. objAsB.Foo(); //call: prints B
    33. objAsC.Foo(); //call: prints C
    34. objAsA.Foo(); //callvirt: prints B
    35.  
    This is because when objAsB it's a direct call to the sealed method. The compiler knows exactly where it is.

    When it's objAsC it again is a direct call since it's a sealed 'new' method. It's 'new' so it ignores anything in the parent types, and since it's sealed it knows it doesn't have to use a vtable. And thusly we get the newer "C" result.

    And when it's objAsA it has to use the vtable, but it respects the 'new' behaviour.

    [inserted edit] - note that sealing a class is like sealing each individual member of the class. The compiler knows the class can't be inherited from so even if a method is virtual, if the variable is typed as the sealed class, it knows it doesn't have to look up the object's type pointer because it's going to point to this sealed class.

    ...

    So in the end.

    Inheritance isn't really a culprit for performance issues.

    It's the use of virtual typing which can lead to situations where the compiler can't suss out where things are and instead must wait for runtime. And if it has to wait for runtime, then it may need to load new memory into L1 cache otherwise known as a "cache miss", and this can technically cause slower speeds. Especially on lower end systems, or SoC systems like mobile devices.

    ...

    With that said:

    Though I agree composition is a far better alternative to inheritance in most situations (inheritance still has its place at times... MonoBehaviour is a good example of its inheriting the coupling behaviour between the engine and the scripting environment).

    Technically composition will not resolve any of the performance issues, as the OP appears to be most concerned about. Not without a lot of extra verbosity thrown into the mix, and throwing the concept of encapsulation out the window.

    Effectively if you compose an object into another. You need to uncover that composed object.

    May it be by a property/method/field.

    For example GameObject uses composition to compose multiple components together into the GameObject. But to access them requires calling 'GetComponent' which has the overhead of allocating a stack frame which arguably is more expensive than a lookup in the vtable.

    The same goes for a property, since really a property is just a method with pretty syntax sugar (of course technically the compiler 'could' optimize simple properties... but that's IF the compiler tries to, and said property isn't virtual).

    And fields would definitely allow for much more direct access. But at the cost of having public fields hanging out there... and if they aren't readonly (which is an issue in Unity since the only time you can set readonly fields is at the construction of the object and Unity doesn't like that), this means your field can just be modified all willy-nilly potentially breaking your composition.

    ...

    Of course vtables have this same issue as well. Since once you've looked up your method in the vtable, if it calls the 'base' method via "base.Foo()", then you've also got the call stack chaining up as well allocating stack frames the whole way.

    And of course we never even got into runtime optimizations that the runtime could do once running a segment of code once. If it suss's out a limitation to the virtual stack, it can reduce the overhead when that piece of code is ran again in the future. Of course this relies on a competent runtime, which Unity through its life has not been known for having.

    ...

    TLDR;

    This is a huge conversation with tons of caveats.

    Thing is we're talking about such TINY performance issues that unless you're doing this millions of times per second you're not really going to notice it on modern computers.

    I mean hell, the mere access of Unity side values. Like ANY of the Transform members comes with much larger performance hits on a scale relative to this nano-optimization we're discussing right now. You know, since it has to reach through to the Unity engine from the scripting runtime.
     
    Last edited: Oct 21, 2018
    Marrlie, NotaNaN, PraetorBlue and 4 others like this.
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    No.

    The performance hit the other posters have described will be so small as to be less then the statistical error generated by measuring it. Don't worry about inheritance slowing your code down. Spend your time focused on what makes you a faster developer.
     
  7. SUfIaNAHMAD_

    SUfIaNAHMAD_

    Joined:
    Jun 19, 2019
    Posts:
    18
    Code (CSharp):
    1. using UnityEngine;
    2. public class Player_Controller: Player
    3. {
    4.     void Start()
    5.     {
    6.         p_rb = gameObject.GetComponent<Rigidbody2D>();
    7.     }
    8.     void Update()
    9.     {
    10.         PlayerInput();
    11.     }
    12.     void FixedUpdate()
    13.     {
    14.         PlayerPhysics();
    15.     }
    16. }
    17. public class Player: MonoBehaviour
    18. {
    19.     [SerializeField]
    20.     protected float p_speed;
    21.     protected Rigidbody2D p_rb;
    22.     public states p_states;
    23.     public void PlayerInput()
    24.     {
    25.         if (Input.GetKey("a"))
    26.         {
    27.             p_states = states.p_left;
    28.         }
    29.         else if (Input.GetKey("d"))
    30.         {
    31.             p_states = states.p_right;
    32.         }
    33.         else
    34.         {
    35.             p_states = states.p_break;
    36.         }
    37.     }
    38.     public void PlayerPhysics()
    39.     {
    40.         if (p_states == states.p_left)
    41.         {
    42.             p_rb.velocity = new Vector2(-p_speed * Time.deltaTime, 0f);
    43.         }
    44.         else if (p_states == states.p_right)
    45.         {
    46.             p_rb.velocity = new Vector2(p_speed * Time.deltaTime, 0f);
    47.         }
    48.         else
    49.         {
    50.             p_rb.velocity = new Vector2(0f, 0f);
    51.         }
    52.     }
    53. }
    54.  
    a


    I think it affects the performance as in this code whenever I move the object it gets lag but before that, it was working fine. If anyone has a better opinion please share, I am really curious about what I have to use Inheritance or not as it is super cool but in my case, it looks weird :(
     
    Last edited: Sep 12, 2020
  8. SUfIaNAHMAD_

    SUfIaNAHMAD_

    Joined:
    Jun 19, 2019
    Posts:
    18
    But when it is a critical hyper-casual game in which you have to tab at milliseconds, my controller gets the worst As the input and the physics system get some delay which can be a reason for the lag in the movement.

    What you will suggest in this type of case
     
  9. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Your code isn't even using virtual methods. If you have a performance problem, it has nothing to do with inheritance.
     
    SUfIaNAHMAD_ likes this.
  10. SUfIaNAHMAD_

    SUfIaNAHMAD_

    Joined:
    Jun 19, 2019
    Posts:
    18
    I believe you are right as Google gives the same quote that inheritance doesn't affect the performance. If anyone wants to test you can use Time.realtimeSinceStartupNow, I tested and now I will refactor my project with polymorphism, as it is good for it. Thanks to all for sharing experiences :)