Initializing Serialized Fields - Double Allocation?

Hello, I’ve been wondering this for ages but have never been able to divine the answer.

If I have a class with a serialized field that I initialize to a default value, does a second allocation then happen under the hood when Unity deserializes an instance of that class? For example:

{
[SerializeField]
private List<int> m_list = new List<int>(10);
}```
It would seem a new List<int> is first allocated on the heap when my class is instantiated, then a second one would be allocated when Unity pulls the data out of my serialized object, making the first allocation now available for GC.

However, if I run this test with my own [Serializable] class with a Log statement in its default constructor, I only see my log message once. Surprising! So, how does Unity achieve this? Am I wrong in thinking serialized fields are allocated twice?

Any insight would be great, just trying to level up our memory usage for the next project. Thanks for any insight anyone has!

Possibly. Remember the actual construction of classes is NOT done in the Unity main thread but rather from the loading thread, so technically you cannot safely call any Unity classes from there. This might explain your Debug.Log() not showing.

I’ve actually always wondered this too.

I did a log out to a text file to make sure I’m not messing with the unity thread stuff. This is what I did:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class zTest03 : MonoBehaviour
{

    public zTest03()
    {
        System.IO.File.AppendAllText(@"C:\zTemp\blargh.txt", string.Format("{0}: {1:hh:mm:ss.ffff}\r\n", nameof(zTest03), System.DateTime.Now));
    }

    public Blargh Value = new Blargh();

    private void Awake()
    {
        System.IO.File.AppendAllText(@"C:\zTemp\blargh.txt", string.Format("{0}: {1:hh:mm:ss.ffff}\r\n", nameof(Awake), System.DateTime.Now));
    }


    [System.Serializable]
    public class Blargh
    {

        public string Message;

        public Blargh()
        {
            System.IO.File.AppendAllText(@"C:\zTemp\blargh.txt", string.Format("{0}: {1:hh:mm:ss.ffff}\r\n", nameof(Blargh), System.DateTime.Now));
        }
    }

}

And my results when pressing play are:

zTest03: 07:09:23.2980
Blargh: 07:09:23.3828
zTest03: 07:09:24.0943
Blargh: 07:09:24.0991
Awake: 07:09:24.1245

Now… first things first… I notice it creates the script itself twice in quick succession after another. Which is weird and likely a quark of the way Play works.

So to clear out any weirdness that the editor just happens to do when you press Play. I’m going to create a dummy scene which then loads a scene with this zTest03 in it when I press the space bar. See what happens.

And this is what I got then:

LoadSceneOnPressSpaceBar: 07:15:40.1101
zTest03: 07:15:40.1363
Blargh: 07:15:40.1405
Awake: 07:15:40.1648

And yeah… it was just some weirdness when you press Play.

And we also confirm that this class is only instantiated once and not twice.

How???

Well… I could think of 2 ways.

One is they instantiate the script (both the unity side and the C# side). Then when they loop over the fields to instantiate each member they check if an object already exists and use that instead of creating a new instance… and if it’s null it creates a new instance.

Another… maybe when they instantiate the script they directly access the heap and create the instance themselves. Note that Unity does use their own version of the mono runtime that they’ve added various tweaks to over the years. This could easily be one of them where they’ve tightly integrated their serialization engine and the memory manager.

Though… I bet it’s the first one.

This is likely the answer, some form of the JSON “PopulateObject” call, which basically leaves the object alone if it exists, replacing only the fields that come in from the serialized YAML text.

They might even have their own “Clear()” implemented by reflecting all fields that meet the serialization qualifications and zeroing / nulling them, THEN applying the serialized data to get consistent behavior… but the way private fields in ScriptableObjects persist forever during an editor run makes me think they do not clear all fields.

Actually you know what…

I bet it’s definitely number 1

And it would explain something that used to annoy the crap out of me back in the day.

Unity used to not support serializing structs! Which was really weird because if they just used the built in reflection/memory manager it’s directly supported. see: Activator.CreateInstance.

And yeah, I bet their logic was checking if an instance existed (which you can’t do with a struct since it’s not a ref type). And so it required extra work on their part to implement logic to specifically treat structs different from classes which they didn’t do until later when they finally added struct support.

That and the first one is just the easiest… who wants to screw around in the mono runtime when you could just use the existing features/tools.

Awesome detective work guys, I agree that number 1 is the most plausible answer. I can finally stop fretting over initializing my serialized fields.

As with any middleware, you kind of just have to make the leap of faith that they’ve made it as efficient and idiot-proof as possible and C# and Unity continue to do a pretty good job at that.

Thanks for the replies!

Unity is an absolute beast. It’s not perfect but oh my goodness it is pretty darned awesome for all the things it can do under the huge range of platforms it targets, from low-end Android throwaway phones up to the latest graphics cards!

2 Likes