0

Problem

My current project implements a pool of objects to avoid constant memory allocation and de-allocation for speed purposes, the object pool is a std::vector<object> and I would like to implement a form of garbage collection to reduce memory usage and increase performance. Every loop the program iterates over the entire vector and if the object is active, executes the update function, this means that if my vector is full of inactive objects, I will be wasting a lot of time iterating over them and also memory storing them. I cannot clean the vector every frame as this would crush performance.

Current attempt

My current attempt at implementing this has been to measure the update time, and use a pre-defined function to determine whether or not we are spending too much time on the update, for the amount of objects currently active, if we are, I clean the vector once to allow the speed to return to normal.

#include <chrono>

void updateObjects()
{
  auto begin = std::chrono::high_resolution_clock::now();
  //update all objects
  for(auto o : objectVec)
  {
    //only update active objects
    if(o.m_alive)
    {
      o.update();
    }
  }
  //end time of update
  auto end = std::chrono::high_resolution_clock::now();
  //calculate time taken vs estimated time
  auto elapsed = (end-begin).count();
  //Estimate is based on performance testing
  long estimate = 25*m_particleCount+650000;

  //If we have no active objects, 
  //but are wasting memory on storing them, we clean up
  //If the update takes longer than it should, we clean up
  if(((objectCount <= 0) && (objectVec.size() > 0)) || (elapsed > estimate))
  {
    cleanVec(); //remove inactive objects
  }
}

This solution works well on my pc, however I am having issues with it on other computers as the time taken for the update to complete varies due to different CPU speeds, and my pre-defined function then doesn't work as it is based off of incorrect data. I am wondering if there is another measurement I can use for this calculation? Is there a way I can measure the pure amount of instructions executed, as this would be the same across computers, some would simply execute them faster? Any other suggestions are welcome, thank you!

Edit

The object pool can be as large as 100,000 objects, typical usage will range from 3000 to the maximum. My function for cleaning is:

objectVec.erase(std::remove_if(objectVec.begin(),
                               objectVec.end(),
                               [](const object& o) {return !(o->m_alive);}),
                               objectVec.end());
Community
  • 1
  • 1
nitronoid
  • 1,459
  • 11
  • 26

2 Answers2

1

Typically, an object pool uses aging to determine when to expel an individual pool object.

One way to do that is to keep an iteration counter within your update loop. This counter would be incremented every loop. Each object could keep a "last active" time (the loop count the last time the object was active). Inactive objects would be moved to the end of the array, and when old enough would be expelled (destroyed). A separate index of the last active object would be kept, so looping for updates could stop when this was encountered.

If it isn't possible to store an active time in an object, you can still move the inactive objects to the end of the active list, and if there are too many inactive objects pare the list down some.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • Firstly, thanks for your answer! I have considered this concept and I do keep track of the amount of times I loop, as well as the birth time and life span of every object, so this solution wouldn't be difficult to implement. My concern is that it could lead to a scenario where I need to clean up every frame. Imagine I add 1000 objects, each with a life span one greater than the previous, I would need to clean up every frame for 1000 frames and I'm worried that this may be costly. – nitronoid May 10 '17 at 09:54
1

You should specify how expensive your cleanup is, and how large your pool is, they affect how cleaning up should be implemented.

To make it clear, the pool is effectively an allocator of one type of object and performance gains are completely independent of how individual objects are cleaned up.

I cannot clean the vector every frame as this would crush performance.

If the performance of inactive objects' cleanup dominates, there is nothing you can do in your pool algorithm.

Therefore I assume this is due to std::vector semantics where removing inactive objects involves relocation of other objects. It is then logical to ask do you really need std::vector's properties?

  • Do you need your objects to be contiguous in memory?
  • Do you need O(1) random access?
  • Do you loop over the pool constantly?
  • Otherwise, is the pool small? As long as it is small std::vector is fine.

If the answer is no, then just use stuff like std::deque and std::list, where removal is O(1) and you can cleanup on every frame.

Otherwise garbage collection of your pool can be done as

  • keeping a counter of frames since last updated for each object, remove if counter is over a threshold
  • keeping a counter of total inactive objects, clean pool if percentage of active objects is over a threshold
Passer By
  • 19,325
  • 6
  • 49
  • 96
  • I have added some extra information, I can't give an exact figure for the time cost, because as I mentioned in the post, the time differs from computer to computer. I chose `std::vector` as it has been proven to be faster to iterate over, as storing the objects contiguously prevents cache misses. I do like your suggestion for the active percentage, I will test that implementation so thank you! – nitronoid May 10 '17 at 12:46