Last time we saw that calling a non-default constructor on a generic struct (MyStruct<T>) causes garbage creation. That garbage creation is subtle, but can have big impacts on framerate and memory usage. Today we’ll see two more ways that structs can create garbage and hopefully avoid some pitfalls. Read on to find out how!

To recap, here’s the test from last time:

using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	struct NormalStruct
	{
		public int Val;
 
		public NormalStruct(int val)
		{
			Val = val;
		}
	}
 
	struct GenericStruct<T>
	{
		public T Val;
 
		public GenericStruct(T val)
		{
			Val = val;
		}
	}
 
	void Start()
	{
		TestNormalStructDefaultCtor();
		TestNormalStructParamsCtor();
		TestGenericStructDefaultCtor();
		TestGenericStructParamsCtor();
	}
 
	void TestNormalStructDefaultCtor()
	{
		var s = new NormalStruct();
		s.Val = 0;
	}
 
	void TestNormalStructParamsCtor()
	{
		new NormalStruct(0);
	}
 
	void TestGenericStructDefaultCtor()
	{
		var s = new GenericStruct<int>();
		s.Val = 0;
	}
 
	void TestGenericStructParamsCtor()
	{
		new GenericStruct<int>(0);
	}
}

None of the tests created any garbage except the one that called the constructor with parameters on a generic structure: new GenericStruct(0);. This made me wonder what else would cause garbage creation with a generic structure, so I added some more test cases:

  • Get a field
  • Set a field
  • Get a property
  • Set a property
  • Call a function that returns a field
  • Call a function that sets a field

Which of these will create garbage? Will any of them create garbage when used with a non-generic structure? To find out, I ran this test script in Unity 5.3.4f1 with the profiler set to “deep” mode:

using UnityEngine;
 
public class TestScript : MonoBehaviour
{
	struct NormalStruct
	{
		public int Val;
 
		public NormalStruct(int val)
		{
			Val = val;
		}
 
		public int ValProp { get { return Val; } set { Val = value; } }
		public int GetVal() { return Val; }
		public void SetVal(int val) { Val = val; }
	}
 
	struct GenericStruct<T>
	{
		public T Val;
 
		public GenericStruct(T val)
		{
			Val = val;
		}
 
		public T ValProp { get { return Val; } set { Val = value; } }
		public T GetVal() { return Val; }
		public void SetVal(T val) { Val = val; }
	}
 
	void Start()
	{
		TestNormalStructDefaultCtor();
		TestNormalStructParamsCtor();
		TestNormalStructGetField();
		TestNormalStructSetField();
		TestNormalStructGetProp();
		TestNormalStructSetProp();
		TestNormalStructGetFunc();
		TestNormalStructSetFunc();
		TestGenericStructDefaultCtor();
		TestGenericStructParamsCtor();
		TestGenericStructGetField();
		TestGenericStructSetField();
		TestGenericStructGetProp();
		TestGenericStructSetProp();
		TestGenericStructGetFunc();
		TestGenericStructSetFunc();
		TestGenericStructMultiple();
	}
 
	void TestNormalStructDefaultCtor()
	{
		var s = new NormalStruct();
		s.Val = 0;
	}
 
	void TestNormalStructParamsCtor()
	{
		new NormalStruct(0);
	}
 
	int TestNormalStructGetField()
	{
		var s = new NormalStruct();
		return s.Val;
	}
 
	void TestNormalStructSetField()
	{
		var s = new NormalStruct();
		s.Val = 0;
	}
 
	void TestNormalStructGetProp()
	{
		var s = new NormalStruct();
		var v = s.ValProp;
	}
 
	void TestNormalStructSetProp()
	{
		var s = new NormalStruct();
		s.ValProp = 0;
	}
 
	int TestNormalStructGetFunc()
	{
		var s = new NormalStruct();
		return s.GetVal();
	}
 
	void TestNormalStructSetFunc()
	{
		var s = new NormalStruct();
		s.SetVal(0);
	}
 
	void TestGenericStructDefaultCtor()
	{
		var s = new GenericStruct<int>();
		s.Val = 0;
	}
 
	void TestGenericStructParamsCtor()
	{
		new GenericStruct<int>(0);
	}
 
	int TestGenericStructGetField()
	{
		var s = new GenericStruct<int>();
		return s.Val;
	}
 
	void TestGenericStructSetField()
	{
		var s = new GenericStruct<int>();
		s.Val = 0;
	}
 
	void TestGenericStructGetProp()
	{
		var s = new GenericStruct<int>();
		var v = s.ValProp;
	}
 
	void TestGenericStructSetProp()
	{
		var s = new GenericStruct<int>();
		s.ValProp = 0;
	}
 
	int TestGenericStructGetFunc()
	{
		var s = new GenericStruct<int>();
		return s.GetVal();
	}
 
	void TestGenericStructSetFunc()
	{
		var s = new GenericStruct<int>();
		s.SetVal(0);
	}
 
	int TestGenericStructMultiple()
	{
		var s = new GenericStruct<int>(0);
		var v = s.ValProp;
		s.ValProp = 0;
		v = s.GetVal();
		s.SetVal(0);
		return v;
	}
}

On the initial run, only TestGenericStructParamsCtor created any garbage: 32 bytes. However, I commented out that test and suddenly another test created 32 bytes of garbage: TestGenericStructGetProp. I don’t know why it didn’t create any garbage on the first run when TestGenericStructParamsCtor was enabled, but if you do drop me a line in the comments.

I kept commenting out tests that created garbage until none were left that created any. In all, these tests each created 32 bytes of garbage:

  • TestGenericStructParamsCtor
  • TestGenericStructGetProp
  • TestGenericStructSetProp
  • TestGenericStructGetFunc
  • TestGenericStructSetFunc
  • TestGenericStructMultiple

Notice that TestGenericStructMultiple is a conglomeration of all the other tests that create garbage. It shows that the amount created is for the struct itself, not the function calls being made on it. It’s simply the usage of the functions that necessitate garbage creation in the first place.

The takeaway here is that any function, non-default constructor, or property on a generic struct will cause that struct to be created as garbage when you use new. You’re free to read or write the fields directly or use the default (no parameters) constructor without creating any garbage, even when the struct is generic. Without using generics, you’re free to use any of these features without fear of garbage creation.

If you’ve got any insight into why garbage is sometimes created with generic structures, let me know in the comments. Or feel free to share your experiences with garbage creation and structs.