object-pool

Table of Contents

Pool

Object Pool

Intent

Improve performance and memory use by reusing objects from a fixed pool instead of allocating and freeing them individually.

Motivation

The Object pool pattern allows us to quickly allocate and deallocate objects.

The Curse of fragmentation

Imagine you have a 16-byte chunk of memory:

But there’s one two-byte chunk in the middle, so all you have is an 8-byte chunk and a 6-byte chunk.

You get a request for a 12-byte chunk, which you could allocate size wise, but you can’t allocate because you don’t have a free 12-byte chunk.

Keep in Mind

Normally you use new and delete to handle memory management for you. Here are some things to keep in mind:

  1. The pool may waste memory on unneeded objects
  2. Only a fixed number of objects can be active at any one time: What happens when we need to allocate a new object?

Memory size of each object is fixed

Unused objects will remain in memory

Sample Code

Start by implementing a simple particle class:

class Particle {
public:
  Particle()
  : framesLeft_(0)
  {}

  void init(double x, double y,
            double xVel, double yVel, int lifetime) {
    x_ = x; y_ = y;
    xVel_ = xVel; yVel_ = yVel;
    framesLeft_ = lifetime;
  }

  void animate() {
    if (!inUse()) return;

    framesLeft_--;
    x_ += xVel_;
    y_ += yVel_;
  }

  bool inUse() const { return framesLeft_ > 0; }

private:
  int framesLeft_;
  double x_, y_;
  double xVel_, yVel_;
};

And then we create a pool that owns all these particles:

class ParticlePool {
public:
  void create(double x, double y,
              double xVel, double yVel, int lifetime);

  void animate() {
    for (int i = 0; i < POOL_SIZE; i++) {
      particles_[i].animate();
    }
  }

private:
  static const int POOL_SIZE = 100;
  Particle particles_[POOL_SIZE];
};

A new particle can be created as well:

void ParticlePool::create(double x, double y,
                          double xVel, double yVel,
                          int lifetime) {
  // Find an available particle.
  for (int i = 0; i < POOL_SIZE; i++) {
    if (!particles_[i].inUse()) {
      particles_[i].init(x, y, xVel, yVel, lifetime);
      return;
    }
  }
}

You’ll notice that every time we want to allocate more memory, this costs O(n) time to traverse to the end of the pool. We can get away with using a union and a free list:

class Particle {
public:
  // ...

  Particle* getNext() const { return state_.next; }
  void setNext(Particle* next) { state_.next = next; }

private:
  int framesLeft_;

  union {
    // State when it's in use.
    struct {
      double x, y;
      double xVel, yVel;
    } live;

    // State when it's available.
    Particle* next;
  } state_;
};

The particle pool keeps a pointer to the first available particle:

class ParticlePool {
  // ...
private:
  Particle* firstAvailable_;
};

Then we provide a constructor:

ParticlePool::ParticlePool() {
  // The first one is available.
  firstAvailable_ = &particles_[0];

  // Each particle points to the next.
  for (int i = 0; i < POOL_SIZE - 1; i++) {
    particles_[i].setNext(&particles_[i + 1]);
  }

  // The last one terminates the list.
  particles_[POOL_SIZE - 1].setNext(NULL);
}

And a way to create a particle:

void ParticlePool::create(double x, double y,
                          double xVel, double yVel,
                          int lifetime) {
  // Make sure the pool isn't full.
  assert(firstAvailable_ != NULL);

  // Remove it from the available list.
  Particle* newParticle = firstAvailable_;
  firstAvailable_ = newParticle->getNext();

  newParticle->init(x, y, xVel, yVel, lifetime);
}

And when a particle dies, we add it back to the free list:

bool Particle::animate() {
  if (!inUse()) return false;

  framesLeft_--;
  x_ += xVel_;
  y_ += yVel_;

  return framesLeft_ == 0;
}

Threading it back properly:

void ParticlePool::animate() {
  for (int i = 0; i < POOL_SIZE; i++) {
    if (particles_[i].animate()) {
      // Add this particle to the front of the list.
      particles_[i].setNext(firstAvailable_);
      firstAvailable_ = &particles_[i];
    }
  }
}

Prev: dirty-flag Next: spatial-partition