Wednesday, June 10, 2015

A simple way to reduce memory pressure on Unity iOS (and maybe Android) titles

Here's how to control the tradeoff between memory collection frequency and Mono memory headroom in Unity titles. I've tested this on iOS but it should work on any platform that uses LibGC to manage the garbage collected heap. (It would be nice if Unity exposed this in a simple way..)

The garbage collector used by Unity (LibGC - the Boehm collector) is quite greedy (its OS memory footprint only increases over time), and it allocates more RAM than strictly needed to reduce the frequency of collections. By default LibGC uses a setting that grows the heap by approx. 33% more RAM than needed at any time:

GC_API GC_word GC_free_space_divisor;

/* We try to make sure that we allocate at     */
/* least N/GC_free_space_divisor bytes between */
/* collections, where N is the heap size plus  */
/* a rough estimate of the root set size.      */
/* Initially, GC_free_space_divisor = 3.       */
/* Increasing its value will use less space    */
/* but more collection time.  Decreasing it    */
/* will appreciably decrease collection time   */
/* at the expense of space.                    */
/* GC_free_space_divisor = 1 will effectively  */
/* disable collections.                        */                                            

The LibGC internal function GC_collect_or_expand() bumps up the # of blocks to get from the OS by a factor controlled by this global variable. So if we increase this global (GC_free_space_divisor) LibGC should use less memory. Luckily, you don't need Unity source code to change this LibGC variable because it's global. (Note: At least on iOS. On other platforms with dynamic libraries changing this sucker may be trickier - but doable.)

In our game (Dungeon Boss), without changing this global, our Mono reserved heap size is 74.5MB in the first level. Setting this global to 16 at the dawn of time (from our main script's Start() method) reduces this to 61.1MB, for a savings of ~13.3MB of precious RAM. The collection frequency is increased, because the Mono heap will have less headroom to grow between collections, but it's still quite playable. 

Bumping up this divisor to 64 saves ~23MB of RAM, but collections occur several times per second (obviously unplayable).

To change this global in Unity iOS projects, you'll need to add a .C/CPP (or .M/.MM) file into your project with this helper:

typedef unsigned long GC_word;
extern GC_word GC_free_space_divisor;

void bfutils_SetLibGCFreeSpaceDivisor(int divisor)
{
   GC_free_space_divisor = divisor;
}

While you're doing this, you can also add these two externs so you can directly monitor LibGC's memory usage (directly bypassing Unity's Profiler class, which I think only works in Development builds):

extern "C" size_t GC_get_heap_size();
extern "C" size_t GC_get_free_bytes();

Now in your .CS code somewhere you can call this method:

[DllImport("__Internal")] private static extern void bfutils_SetLibGCFreeSpaceDivisor(int divisor);

public static void ConfigureLibGC()
{
// The default divisor is 3. Anything higher saves memory, but causes more frequent collections.
bfutils_SetLibGCFreeSpaceDivisor(16);
}

Just remember, if you change this setting LibGC will collect more frequently. But if your code uses aggressive object pooling (like it should) this shouldn't be a big deal. 

Also note that this global only impacts the amount of Mono heap memory headroom that LibGC tries to keep around. This global won't help you if you spike up your Mono heap's size by temporarily allocating very large blocks on the Mono heap. Large temp allocations should be avoided on the Mono heap because once LibGC gets its tentacles on some OS memory it doesn't ever let it go.


Monday, June 1, 2015

iOS Memory Pressure Hell: Apple's 64-bit requirement+Unity's IL2CPP tech

Apple's 64-bit requirement combined with the newness of Unity's promising new IL2CPP technology (which you must use to meet Apple's 64-bit requirement) is costing us a serious amount of memory pain:


IL2CPP uses around 30-39MB more RAM for our 32-bit build. We've been struggling with memory on iOS for a while (as documented here). I honestly don't know where to find 30MB more RAM right now, which means our title on 512MB devices (like the iPhone 4s and iPad Mini) is now broken. Crap.

Some details:

Apple is requiring app updates to support 64-bit executables by June 1st. We've known and have been preparing to ship a 64-bit build of our first product for several months. The only way to create a 64-bit iOS build is to use Unity's new IL2CPP technology, which compiles C# code to C++:

The Future of Scripting in Unity
An Introduction to IL2CPP Internals
Unity 4.6.2 iOS 64-bit Support

The additional memory utilized by IL2CPP builds vs. the previous Mono-AOT approach is ~10MB for our code with an empty scene, and >30MB with everything running.

We've also tested the latest patch, Unity 4.6.5p4 (which is supposed to use less memory on iOS) with the same results.