Object Pooling in Xen

Object Pooling is a memory management and performance optimization technique that pre-allocates a set of objects in a "pool" and assigns them when they are requested. Perhaps the best example of object pooling is the thread pool available in .net. This pattern is useful when:
  1. Object construction is time consuming
  2. Objects are heavy-weight and take up a lot of memory
  3. Allocating new objects would potentially cause the garbage collector to run

In Xen, we are primarily concerned with point #3. Windows Phone 7 and Xbox 360 use the .net Compact Framework, which has a less sophisticated garbage collection algorithm than the full .net CLR found on PCs. Whenever 1MB of memory is allocated from the managed heap, the garbage collector is run (there is no concept of generations). When this happens, the foreground app (your game in this case), will drop frames depending on the time it takes for garbage collection to complete. For typical heaps on Windows Phone 7, garbage collection takes approximately 80ms, which would cause your game to drop 3 frames if it is running at 30 fps. Stuttering or graphical sluggishness creates a terrible user experience. This is where judicious use of object pools can help. By pooling objects, they can be acquired whenever needed, thus avoiding allocation from the managed heap. Some examples of game objects that should be pooled:
  • Bullets
  • Enemies
  • Particles
  • Coins

It makes sense to reuse these objects from a pool because they can be recycled and reused within the same game scene. As an example, let's say your shooter game had the player use a total of 5000 bullets. Not all of those bullets were ever on screen at the same time, so a pool of perhaps 50 would have sufficed. Those extra 4950 bullets would have been new'd up and then promptly collected once they were marked as garbage.

Type-specific vs. Type-generic Pools
When implementing objects pools, there are two common variations: type-specific pools and type-generic pools. As the name implies, type-specific pools are object pools that contain only a specific type of object. In our prior example, a pool of bullets would contain only bullets and no other types. An alternative is to have a type-generic pool that potentially contains multiple different types of objects: bullets, coins, etc. The problem with type-generic pools is that games do not typically feature interchangeable polymorphic game entities. If the player shoots a gun, it probably shoots a specific kind of bullet. Type-specific object pools make more sense in games and this is the solution Xen uses.

Acquire and Release instead of New and Delete
When you would normally call "new" to create an object on the managed heap, if the object is a PooledObject, you can call Acquire() instead. Acquiring will return an unused instance of the object from the pool. When you are done with the object, make sure to call Release() so that the object can be reclaimed by the pool and reused in the future. Acquiring objects without releasing them is very bad because it will claim all the available objects in the pool. Worse yet, since the pool retains references to all instances of that particular type, the garbage collector will conclude that all instances are in use and never reclaim that memory. For all purposes, acquiring pooled objects without releasing them is considered a memory leak.

Initializing the Pool Size
One drawback of object pools is that they can run out of instances. If all existing objects of a pool are in use and your code tries to acquire another instance, the pool will automatically expand itself and allocate a new instance of the object for you, and then return it. Having the pool call "new object" obviously defeats the purpose of avoiding allocations from the managed heap, so it is extremely important to initialize the size of your object pools during times in your game when frame rate is not important. For example, when the game launches and loads its assets, this is a good time to also initialize your object pools. Try to choose a pool size that is large enough to serve the application's needs without having to resize during performance critical segments (such as gameplay) but do not make the pool size too large or else your game will take up too much memory. When in doubt, benchmark your game to discover the maximum number of instances of a particular object that might be needed.

Creating your own pooled object type
Creating your own custom pooled object is easy; derive from the PooledObject base class in XenAspects.dll.
public class MyPooledObject : PooledObject<MyPooledObject>
{
}

During initialization/LoadContent, you'll want to initialize the pool size.
MyPooledObject.Pool.InitPool( 100 );

Instead of new'ing up an instance of MyPooledObject, use Acquire(). Don't forget to Release when you're done with it!
var instance = MyPooledObject.Acquire();
//Do something with instance
instance.Release();

TODO: Reset, Release/Cleanup, ComposableObject

Last edited Feb 8, 2011 at 11:19 AM by robzhu, version 13

Comments

No comments yet.