On a mobile platform, memory is to be considered seriously. Unlike a desktop PC where we have 2, 4 (8?) GB to live on, the general feeling about the earlier iPhone and iTouch is that we have around 20MB to play with. Tiny sandbox.

The bottom line

If you start with one of the iPhone OS templates/examples, you likely have a view controller with an empty method like this:

- (void)didReceiveMemoryWarning {}

The bottom line is that it seems safer to arrange that your app does not trigger a memory warning under normal usage conditions. I haven’t really seen this written anywhere, just commonsense, where normal conditions would mean:

  • You haven’t just rebooted the device. If you’ve just rebooted, you are in ‘perfect conditions’. At the moment I tested my app and it triggers the memory warning 12 times or so. If I reboot and perform the same test, the warning never gets called.
  • You are not testing right after another app has crashed. Maybe this makes no difference, but it feels like abnormal conditions.
  • Your test involves normal gameplay, and lasts for the maximum expected duration for a play session.
  • You are testing on the tightest configuration possible (e.g. iTouch 2nd gen, 8GB, running OS4)

You can ‘recover’ from a memory warning – maybe you just need to free enough memory, and since my app did not crash after receiving 12 memory warnings, you might even think it’s OK. Now, do you really want to be running on the edge of disaster? I don’t think so.

What Instruments say

When I get a memory warning, instruments are displaying memory as follows:

  • 30 MB (process allocation) in Activity Monitor
  • 20 MB (live bytes) used by my process in ObjectAlloc

These values are consistent with the rumored 20MB, and also consistent with repeated complaints that different instruments provide different values. ObjectAlloc measures  live bytes – memory used by an app; activity monitor measures (a variable amount of) memory allocated to a process. I tend to give more consideration to what ObjectAlloc says, because this is easier to understand, easier to predict and easier to control. There are additional conditions that will cause a process to use more memory:

  • If we keep allocating / deallocating blocks of various sizes, we fragment memory and promote semi-transient waste.
  • It may be nearly impossible not to leak any memory over time (especially if we need a system library that leaks memory). Leaked memory is lost forever, and won’t show in ObjectAlloc.

So what do we need to do?

I am targeting 15MB max as peak allocation in ObjectAlloc. I think it should be possible to play my game end to end without exceeding this value and use this as ‘maximum expected play session duration’. I have fixed all known and known to be fixable memory leaks in my product; I want to have a go and try to find out if I can work around leaks in system libraries. I don’t expect to be able to fix all of them.

I have implemented didReceiveMemoryWarning – so I have a mechanism to release unused resources. Once we’ve done that, we want to release resources more often, so we stay under the maximum we choose (in my case 15MB). If we’re really tight, we might want to arrange assets to be lighter (e.g. use models with a lower poly count if we’re talking 3D apps)

A quick case study

My game is a 3D game. the bundle is about 17MB worth of data, including 1MB worth of code. I’m not using textures; I’m loading models on the fly, so I have no loading times.

I have two problems:

  • Except procedurally generated geometry, my allocations never expire unless a warning triggers the release mechanism
  • Most resources reload almost immediately. Why? because I test bounds for visibility (and other things). However bounds are not stored in separate files. In fact bounds are evaluated when an object loads. While the processing overhead is negligible, bounding boxes are released along with geometry.

If it was to do all over again, I might just store bounds separately. This might help deferring loading an object, so my ‘loading time’ for a stage would drop from about 1 second to nearly nothing – or maybe not – at the moment it doesn’t even seem asset loading is the main cost in scene setup.

As it stands, I am not doing it all over again; instead I want to retain bounds when I release assets, and release assets in a timely manner. While I’m doing this I also want to never release geometry that needs be displayed at the next frame. My resource release mechanism doesn’t know about that.

How to know when a resource is ready to be released?

In my rendering loop, there is a procedure that checks whether objects are likely to enter the viewing range. This is a quick and dirty way to skim the complete list of objects in the 3D scene. This procedure gets called less often than a regular visibility check – so the renderer needn’t check all the scene all the time (there are significantly better ways to code this).

So my first idea is, use a skip count. If an object hasn’t entered the near range for a while, we’re OK to release. I wouldn’t set the skip count too low – no need to release resources to often, as whenever we reallocate them, we actually incur memory (fragmentation) and performance (loading) overheads.

It looks good enough, nothing super-clever but OK. But… there is a catch.

Surely geometry is shared – in my implementation, Element is just a token representing an instance of an Object3D – shared geometry reused over and over. That’s where reference count comes in play, right? Whenever an Element instance determines that it doesn’t need its geometry, it releases its instance of Object3D and when the reference count reaches zero, Object3D gets deallocated, correct?

geometry is also mapped from file paths. So when a new element is created, we get the model from the map. Needless to say the map is also retaining each object once. As it is, all elements may release their instance of a geometry object, the map won’t know about it and we still haven’t got the geometry to deallocate.

There are ways around this problem, but we want to do things without wasting either time or too much memory, or changing the existing structures. Here’s the plan:

  1. Each geometry node will have used flag. Not a counter, just a boolean.
  2. Initially the used flag is clear (false).
  3. Whenever we update the display list, every object that’s nearby sets the used flag.
  4. From time to time (every 1 to 5 minutes, say), we iterate the list of values in the map, then…
  5. We release all models for which the used flag is clear – these models haven’t been used in a while, likely we won’t need them soon.
  6. We clear the flag for all remaining models.

I can generalize this a little bit checking accesses from Element3D.

    Implementation?

    I tried to implement the above quickly. Maybe a little too quickly. Ghosting bounds (retaining the bounds after geometry has been unloaded) went fine. Trying to automate memory release (based on the used flag) looked good in theory; in practice, it didn’t really work this time.

    So I did the simple thing. Force release (using the existing mechanism for handling memory warnings) manually when reaching a new stage in the game or performing a key action. The results are displayed below.

    Test Results

    I tested on an iPhone 3GS, mainly to save time. I use more memory on the 3GS because the rendering surface is bigger than the display (so I can antialias). The test glitched a little because I’d introduced a bug that failed the release mechanism between stage 3 and 4 in my game.

    • Startup: 0.6MB
    • Stage 1: 3.8-4.3MB
    • Force release as much as possible: 1.76MB
    • Stage 2: 4.67-10.06 (until Klinnburg village); 5.5 after picking the bread; climb to 12-13 MB
    • Stage 3: 13 – 20.6 (but fails to show chapter 3 notice and release memory when starting)
    • After releasing resources: 5.0mb
    • Stage 4: 12.18-16MB

    Conclusion

    The results of the test suggest that the resource release mechanism is somewhat inefficient. It looks like, if I had just 6 stages like the ones above, I’d hit the 20MB limit.

    Meanwhile, I don’t expect running into problems for this release. I don’t think my app leaked about 20MB in 40 minutes. I’ve done several passes to eliminate all leaks in the app code. One of the latest steps before submitting the app will be checking for leaks again, and looking into leaks generated by system libraries.