Virtual Function Performance

Datetime:2016-08-23 02:53:52          Topic: ActionScript  C#  Unity           Share

One type of function was left out of Unity Function Performance : virtual functions. Functions in C# are non-virtual by default and you have to explicitly use the virtual and override keywords to override them. Why not make this the default, like in AS3 or Java? Are virtual functions that much slower? Today’s article finds out! Should you be worried every time you make a function virtual ?

For some background on virtual functions, seethis article in the From AS3 to C# series that covers how they work. For some behind the scenes information on how virtual functions are implemented, check out the Wikipedia article on dynamic dispatch .

In short, here’s what we’re testing today:

public class Parent
{
	// Virtual function in a parent class
	public virtual void VirtualFunction()
	{
	}
 
	// Non-virtual function in a parent class
	public void NonVirtualFunction()
	{
	}
}
 
public class Child : Parent
{
	// Virtual function in a child class overriding a
	// virtual function in the parent class
	public override void VirtualFunction()
	{
	}
 
	// Non-virtual function in a child class with the
	// same name as the non-virtual function in the
	// parent class
	public new void NonVirtualFunction()
	{
	}
}

And here’s the test script that compares the performance of these four function types.

using UnityEngine;
 
public class Parent
{
	public virtual void VirtualFunction()
	{
	}
 
	public void NonVirtualFunction()
	{
	}
}
 
public class Child : Parent
{
	public override void VirtualFunction()
	{
	}
 
	public new void NonVirtualFunction()
	{
	}
}
 
public class TestScript : MonoBehaviour
{
	private const int NumIterations = 100000000;
 
	private string report;
 
	void Start()
	{
		var stopwatch = new System.Diagnostics.Stopwatch();
		var parent = new Parent();
		var child = new Child();
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < NumIterations; ++i)
		{
			parent.VirtualFunction();
		}
		var virtualParentTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < NumIterations; ++i)
		{
			child.VirtualFunction();
		}
		var virtualChildTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < NumIterations; ++i)
		{
			parent.NonVirtualFunction();
		}
		var nonVirtualParentTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (var i = 0; i < NumIterations; ++i)
		{
			child.NonVirtualFunction();
		}
		var nonVirtualChildTime = stopwatch.ElapsedMilliseconds;
 
		report =
			"Test,Virtual Time,Non-Virtual Time\n"
			+ "Parent," + virtualParentTime + "," + nonVirtualParentTime + "\n"
			+ "Child," + virtualChildTime + "," + nonVirtualChildTime;
	}
 
	void OnGUI()
	{
		GUI.TextArea(new Rect(0, 0, Screen.width, Screen.height), report);
	}
}

If you want to try out the test yourself, simply paste the above code into a TestScript.cs file in your Unity project’s Assets directory and attach it to the main camera game object in a new, empty project. Then build in non-development mode for 64-bit processors and run it windowed at 640×480 with fastest graphics. I ran it that way on this machine:

  • 2.3 Ghz Intel Core i7-3615QM
  • Mac OS X 10.10.3
  • Unity 5.0.2f1, Mac OS X Standalone, x86_64, non-development
  • 640×480, Fastest, Windowed

And got these results:

Test Virtual Time Non-Virtual Time
Parent 235 31
Child 224 30

When called on instances of either the Parent or Child class, virtual functions were about 7.5x slower than non-virtual functions. That’s a major gap and certainly a good reason to allow C# programmers to opt-out of them.

On the flip side, virtual function calls in this test are taking 0.00000235 milliseconds each. You’d need to make about 50,000 such calls to eat up one millisecond of CPU time. That’s a lot of calls, but not entirely out of the question for a complex game. If virtual functions were the only option—as in AS3 and Java—it’s possible that their overhead could appear during profiling as a significant portion of a 16 millisecond frame (60 FPS).

Performance problems might crop up quite a bit quicker than 50,000 function calls per frame. The CPU used for the test is actually quite fast, especially compared to many CPUs found in mobile devices. In rough terms, the test’s CPU is about twice as fast as a mobile CPU. That means you’d only need 25,000 virtual function calls to eat up a millisecond and that’s much more likely.

It’s definitely fine to make a few function calls virtual as it makes you a more productive programmer. However, be wary if you’re going to make thousands of virtual function calls per frame- you might end up with a performance problem.

In the end, should we be glad that functions are non-virtual by default in C#? Let me know your opinion inthe comments.





About List