5

Unity Mono Runtime – The Truth about Disposable Value Types

 3 years ago
source link: https://codingadventures.me/2016/02/15/unity-mono-runtime-the-truth-about-disposable-value-types/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
The Truth about Disposable Value Types ~ Coding Adventures
screen-shot-2016-02-10-at-12-33-41.png

When I started making games using Unity, after almost 10 years of C# development, I was very concerned to acknowledge that foreach loops are highly avoided in Unity because they allocate unnecessary memory on the heap. Personally I love the clean syntax of a foreach. It aids readably and clarity and it also increases the abstraction level. However a very clear and neat explanation of the memory issue problem can be found in a blog article posted on Gamasutra by Wendelin Reich.

From Wendelin’s analysis it emerged that the version of the Mono compiler adopted in Unity has a different behaviour from Microsoft implementation. In particular enumerators, which are usually implemented in the .NET framework as mutable value types, are boxed by the compiler, causing an unnecessary generation of garbage. Boxing is the process of converting a value type (allocated on the stack) into a reference type, thus allocating a new instance on the heap. 

This aspect is really interesting as well as dangerous. As you probably know memory leaks in C# will trigger the GC (garbage collector) too often causing performance spikes and frame drops in your game.

However I think an explanation of why value types are boxed is missing from Wendelin’s article.A very common idea between game developers is that the Mono runtime in Unity has a bug.

To be really honest with you don’t necessarily have to know the reason behind this behaviour. It’s more than enough to know that using foreach loops generate garbage so you can avoid them. However I always have been extremely curious like a cat that checks out an empty box. I believe that curiosity is the biggest source of power for a developer. So if you’re like me, curious to know what’s going on in the old Mono compiler just keep on reading 🙂

Foreach loops are basically syntactic sugar. The C# compiler processes a foreach loop like the following:

void Update()
var fibonacci = new List{ 1, 1, 2, 3, 5, 8, 13 };
foreach(var f in fibonacci)
Console.WriteLine(f);
void Update()
{
    var fibonacci = new List{ 1, 1, 2, 3, 5, 8, 13 };
    foreach(var f in fibonacci)
    {
        Console.WriteLine(f);
    }
}

into something else that essentially looks like this code:

using (var enumerator = fibonacci.GetEnumerator())
while(enumerator.MoveNext())
var current = enumerator.Current;
Console.WriteLine(current);
using (var enumerator = fibonacci.GetEnumerator())
{
    while(enumerator.MoveNext())
    {
        var current = enumerator.Current;
        Console.WriteLine(current);
    }
}

Under the hood the C# compiler uses an Iterator block (based on the Iterator design pattern). The enumerator of the collection being iterated must implement a MoveNext() Dispose() and Reset() method and it must expose the property Current, that holds a reference to the current element. In the .NET framework enumerators (with an exception for KeyCollection and ArrayList) are generally mutable value-types that implements the interface IEnumerator or IEnumerator<T> for generic collections and the interface IDisposable. As you can see the foreach hides the complexity of enumerators, which is really handy!

However the using statement is syntactic sugar too (too many calories for a foreach don’t you think? 😀 ). Its purpose is to obtain a resource, to execute a statement, and then to dispose the resource. The C# compiler translates the using statement into something different. To know how the final code looks like we have two options:

  1. Look at the generated IL (Intermediate Language) by decompiling the function that contains the foreach loop (and thus the using).
  2. Look at the C# specification language document.

Let’s go with the first option and see how the IL code looks like in figure 1. To decompile your assemblies you can either use ILSpy on Windows (which I highly recommend) or monodis, the equivalent command-line Mono implementation of ILDASM, available on MacOS and Linux too.

ILForeach
Figure 1 – The IL code of our foreach loop

As expected the value-type Enumerator is boxed (bottom center in figure 1) and then allocated on the heap. This is of course not a news for any experienced Unity developer. This issue has been addressed in a plethora of articles online (just to mention another good one: REDUCING MEMORY USAGE IN UNITY, C# AND .NET/MONO). However I’m pretty confident that not many developers are able to explain why.

As mentioned earlier, a lot of Unity devs know about this behaviour as a bug in the version 2.6 of the Mono compiler…but is it really a bug? Before jumping to conclusion I’d invite you to have a look at the C# specification document (remember, our option 2), specifically the chapter about using statement in case of non-nullable value types.

From chapter 8.13 of the C# 5 specification document:

A using statement of the form

using (ResourceType resource = expression) statement

corresponds to one of three possible expansions. When ResourceType is a non-nullable value type, the expansion is

ResourceType resource = expression;<br>
statement;
finally
((IDisposable)resource).Dispose();
ResourceType resource = expression;<br>
try 
{
    statement;
} 
finally
{
    ((IDisposable)resource).Dispose();
}

Have you notice anything strange in the code above? Look again…at the finally block. resource is first cast into an a IDisposable object and then the Dispose method is called. Wait a second…IDisposable is an interface…what happens if you cast a value type  into an interface type? The value-type is boxed and allocated on the heap!!!! Boxing makes a copy of the resource, and it is the copy which is disposed, and therefore the copy which is mutated. This is a way to avoid mutating the value type, moreover, within a using statement it is not possible to mutate (i.e. manually changing the fields of) a value type struct.

Now I would like to ask you one more time: is it a bug in the old Mono compiler? It looks like it is definitely NOT. The poor and blamed Mono compiler does exactly what the C# specification states. At this stage you’re probably wondering if the Microsoft C# compiler behaves differently. Let’s have a look at the IL of the same assembly used above but compiled with the Microsoft compiler:

ILForeach2
Figure 2 – IL code of a foreach loop in the Microsoft C# compiler

Surprisingly the disposed resource is not boxed, instead a constrained callvirt is forced in order to avoid boxing. Eric Lippert, a Principal Developer at Microsoft on the C# compiler team and a member of the C# language design team up until 2013, wrote an interesting article on his blog on the topic. It emerges from his article that the Microsoft C# compiler performs an optimisation: if it detects that the Dispose method is exposed directly on the value type then it effectively generates a call to:

finally
resource.Dispose();
finally 
{
   resource.Dispose();
}

The careful reader might notice that this optimisation performed by the compiler is not reported in the C# specification document. In fact, in an answer I found on StackOverflow Eric admits that the optimisation emitted — of using constrained callvirt to skip the boxing when possible — is actually strictly speaking a violation of the C# specification.

This is the reason why in the Microsoft C# compiler (as well as in the latest Mono compiler) the use of foreach, or to be technically correct, the use of disposable structs within a using statement doesn’t generate garbage.

What the old Mono compiler is missing is a specific optimisation, and unfortunately even if Unity has stated that an upgrade to the new Mono compiler is planned, they won’t do it until IL2CPP will be compatible among all mobile platforms.

However I came up with a solution that makes foreach loops garbage free with a lot of different collection (included dictionaries!). I used a third party collection library called C5. C5 is a library of generic collection classes for C# and other CLI languages. It implements a lot of data structures not provided by the standard .Net Framework, such as persistent tree data structures, heap based priority queues, hash indexed array lists and linked lists, and events on collection changes. If you want to have a look here is the link on the GitHub page of the project, and if you want to know more you’ll find an explanation on the new memory safe model I implemented in this thread of the Unity forum I posted a while ago.

That’s all folks! I hope you enjoyed the article, see you soon!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK