Pool
Improve performance and memory use by reusing objects from a fixed pool instead of allocating and freeing them individually.
The Object pool pattern allows us to quickly allocate and deallocate objects.
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.
Normally you use new
and delete
to handle
memory management for you. Here are some things to keep in mind:
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;
particles_[POOL_SIZE];
Particle };
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:
// ...
* getNext() const { return state_.next; }
Particlevoid 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.
* next;
Particle} state_;
};
The particle pool keeps a pointer to the first available particle:
class ParticlePool {
// ...
private:
* firstAvailable_;
Particle};
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.
* newParticle = firstAvailable_;
ParticlefirstAvailable_ = newParticle->getNext();
->init(x, y, xVel, yVel, lifetime);
newParticle}
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