Test Outcome:
In general, interface and override methods are not really a performance consideration. The cost of virtual/override/interface methods is so extremely small (the milliseconds difference in this test are for 100 million iterations) that it's not even worth thinking about it in most situations.
However, we found out;
- Making a method virtual roughly doubles its method-call cost
- Overrides for virtual/abstract methods roughly doubles or triples the method-call cost
- Calling a method through an interface roughly doubles or triples the method-call cost
- Calling a child override method through polymorphism adds a few miliseconds, but not that much
- These costs do not seem to scale with the complexity of the method. This basically means that the costs will be completely irrelevant in most methods that do more than just assigning a value to an int
The Test:
public interface ITestMethod
{
void MethodI(int val);
}
public abstract class AbstractClass
{
public int testInt = 0;
public abstract void MethodAbstract(int val);
}
public class ParentClass : ITestMethod
{
public int testInt = 0;
public void MethodA(int val)
{
testInt = val;
}
public virtual void MethodB(int val)
{
testInt = val;
}
public void MethodI(int val)
{
testInt = val;
}
}
public class ChildClass1 : ParentClass
{
public override void MethodB(int val)
{
base.MethodB(val);
}
}
public class ChildClass2 : ParentClass
{
public override void MethodB(int val)
{
testInt = val;
}
}
public class ChildClass3 : AbstractClass
{
public override void MethodAbstract(int val)
{
testInt = val;
}
}
public class MethodCallsTest : MonoBehaviour
{
public ParentClass parentClass = new ParentClass();
public ITestMethod parentInterface;
public ParentClass parentClassPolymorphism;
public ChildClass1 childClass1 = new ChildClass1();
public ChildClass2 childClass2 = new ChildClass2();
public ChildClass3 childClass3 = new ChildClass3();
public const int iterations = 100000000;
public void Awake()
{
parentInterface = parentClass;
parentClassPolymorphism = childClass1;
}
public void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Stopwatch watch = new Stopwatch();
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
parentClass.MethodA(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodA on parentClass " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
parentClass.MethodB(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodB on parentClass " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
parentClass.MethodI(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodI on parentClass " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
parentInterface.MethodI(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodI through the interface " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
childClass1.MethodB(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodB on childClass1 " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
childClass2.MethodB(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodB on childClass2 " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
parentClassPolymorphism.MethodB(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodB on childClass1 through polymorphism " + watch.ElapsedMilliseconds.ToString());
watch.Reset();
watch.Start();
for (int i = 0; i < iterations; i++)
{
childClass3.MethodAbstract(0);
}
watch.Stop();
UnityEngine.Debug.Log("Calling MethodAbstract on childClass3 " + watch.ElapsedMilliseconds.ToString());
}
}
}
and here are the results for the same test but with a more complex mathematical operation instead of just assigning the value
( testInt = Mathf.CeilToInt(Mathf.Pow(val * Mathf.Sin(val), (float)Math.Atan(val) / 99f)); )