C# has properties similar to AS3’s get and set functions. These functions can even be auto-generated for you, which is very convenient. However, the auto-generated versions don’t expose the so-called “backing field” that the property gets and sets. This brings up a question: is there a performance penalty to using an auto-generated property rather than manually implementing the property so we can directly access the backing field? The get and set blocks are like functions, so are we paying function call overhead for them every time we access our own private pseudo-variables? Finally, could we do even better by skipping fields altogether and working on local variables instead? Today’s article puts all three approaches to the test by analyzing the bytecode that’s generated and the performance within a Unity test environment. Read on to see which way is fastest!

First let’s start with a snippet of code to see what kind of code is being generated by the compiler when we use automatic properties. Those are simply properties where we don’t define the get and set blocks ourselves. Here’s how it looks:

public class Test
{
	public int IntProperty { get; private set; }
 
	public Test()
	{
		IntProperty = 5;
	}
}

Now let’s take a look at the bytecode that’s generated for both the property and the call to set its value. As in the article where I analyzed the bytecode that is automatically generated by C# events, I’ll look at a few common compilers. Unlike that article, I’m using dotPeek by JetBrains to do the decompliation.

First, let’s look at the latest Mono 3.12 compiler that MonoDevelop/Xamarin Studio uses:

  [CompilerGenerated]
  [DebuggerBrowsable(DebuggerBrowsableState.Never)]
  private int \u003CIntProperty\u003Ek__BackingField;
 
  public int IntProperty
  {
    get
    {
      return this.\u003CIntProperty\u003Ek__BackingField;
    }
    private set
    {
      this.\u003CIntProperty\u003Ek__BackingField = value;
    }
  }
 
  public Test()
  {
    base.\u002Ector();
    this.IntProperty = 5;
  }

Here we see that IntProperty has been expanded in just the way we’d expect. A backing field has been declared private with a couple of annotations. The get just returns this backing field and the set just sets it to whatever is passed as value. The line that sets it to 5 just calls the set block. It does not directly access the backing field.

Now let’s look at how Microsoft Visual Studio 2013 compiles the same code:

    [CompilerGenerated]
    private int \u003CIntProperty\u003Ek__BackingField;
 
    public int IntProperty
    {
      get
      {
        return this.\u003CIntProperty\u003Ek__BackingField;
      }
      private set
      {
        this.\u003CIntProperty\u003Ek__BackingField = value;
      }
    }
 
    public Test()
    {
      base.\u002Ector();
      this.IntProperty = 5;
    }

Visual Studio has compiled this code almost exactly like Mono has. The only difference is that Mono also included a [DebuggerBrowsable] annotation on the backing field where Visual Studio did not.

Finally, let’s see how Unity 4.6.2 compiles the code by decompiling Library/ScriptAssemblies/Assembly-CSharp.dll:

  [CompilerGenerated]
  private int \u003CIntProperty\u003Ek__BackingField;
 
  public int IntProperty
  {
    get
    {
      return this.\u003CIntProperty\u003Ek__BackingField;
    }
    private set
    {
      this.\u003CIntProperty\u003Ek__BackingField = value;
    }
  }
 
  public Test()
  {
    base.\u002Ector();
    this.IntProperty = 5;
  }

Unity has compiled the code exactly the same as Visual Studio. All three of them are essentially identical, save for Mono’s additional annotation on the backing field. That annotation shouldn’t have any performance impact at all. For the purposes of this test, the results should apply to all three commonly-used compilers.

Now let’s take a look at a quick test that sums the first billion integers. There are three test cases. The first retrieves the current sum from an auto-generated property, adds to it, then stores it back to the auto-generated property. The second retrieves from and stores to a backing field without using a property. The third uses a local variable and then stores to a backing field once the final sum has been computed.

Here’s how the test looks. If you want to recreate it, just create a new project and attach this script to your main camera.

using System.Diagnostics;
 
using UnityEngine;
using UnityEngine.UI;
 
public class TestScript : MonoBehaviour
{
	private const long NumValues = 1000000000;
 
	public long SumAuto { get; private set; }
 
	private long sum;
	public long SumManual
	{
		get { return sum; }
		set { sum = value; }
	}
 
	private string report;
 
	void Start()
	{
		var stopwatch = new Stopwatch();
 
		stopwatch.Reset();
		stopwatch.Start();
		for (long i = 0; i < NumValues; ++i)
		{
			SumAuto += i;
		}
		var propertyTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		for (long i = 0; i < NumValues; ++i)
		{
			sum += i;
		}
		var fieldTime = stopwatch.ElapsedMilliseconds;
 
		stopwatch.Reset();
		stopwatch.Start();
		long localSum = 0;
		for (long i = 0; i < NumValues; ++i)
		{
			localSum += i;
		}
		sum = localSum;
		var localTime = stopwatch.ElapsedMilliseconds;
 
		report = "Test,Time\n"
			+ "Property," + propertyTime + "\n"
			+ "Field," + fieldTime + "\n"
			+ "Local," + localTime + "\n";
	}
 
	void OnGUI()
	{
		var drawRect = new Rect(0, 0, Screen.width, Screen.height);
		GUI.TextArea(drawRect, report);
	}
}

I tested this app using the following environment:

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

And got these results:

Test Time
Property 1950
Field 1933
Local 628

Summation Performance Graph

The data immediately makes it obvious that directly using the property, even with the auto-generated get and set blocks, is no more expensive than directly using a field. There is no function call overhead, despite the function-like behavior that those blocks have.

But that’s not to say that properties and fields are fast. They’re actually about 3x slower than using a local variable! In cases like these where you are going to do a lot of operations on a field, it’s best for performance if you cache it locally.

Keep in mind though that none of these operations are very expensive on their own. There were, after all, a billion iterations of each loop. That makes the property access just .000001950 milliseconds per operation. You’d need to do a million of them to use about 2 milliseconds. That’s a lot for a single frame though, so much lower numbers may adversely affect performance. It’s more of the kind of thing that won’t show up on a profiler because the field/property accesses are spread throughout the code and add up to a performance problem. Luckily, you can avoid the problem by caching to local variables when justified.

Spot a bug? Have a question or suggestion? Post a comment!