Hi guys,
So I'm warming up myself with C++ again, but my C++ isn't so much before anyway. As I am back, I try to get deeper in memory management.
I am currently trying to create my own generic memory pool for any object that inherits a parent class (I call it Object, which has a static memory pool attached to it). I need this memory pool so I could manually manage any child class that inherits Object.h at any time I, or the pool desire (I guess it's a garbage collector in the end; it is simply a pool).
So every time a class that inherits Object, it will have its new and delete overridden. The "new" will have a normal manual memory allocation, and "delete" simply stores the memory back to the pool in case it is needed again/not going back to free store, unless/until I flush them all either manually/automatically depends on the application needs.
Since children classes are always different, I make the memory pool to be generic per child class, therefore I use STL map. I do this to avoid memory corruption as every child class should have different size of memory required. This causes every memory allocation to check which class to find.
The memory pool works, but the performance is severe. I don't know whether it is the architecture, or STL overuse, or the code is too blunt, but I really need someone to give feedback on it. The performance is like 9x slower compare to default "new" when I test it with 100K iteration (1.8 sec compare to 0.2 sec) when allocating the memory. I even tried another test by filling the pool first before the test but it gets even slower! And I don't even include tracker for the pointers yet.
The thing is, I don't know if this is normal or not. But having it 9 times slower is somehow unacceptable for me. So again, please have a check if I miss something, maybe it's unsafe, the design sucks, or anything you find odd (it contains leaks, corruption, etc.). I need guidance.
Soo here's the code:
MemoryPool.h -> The memory pool!
/**
* Generic stack-based memory pool.
*/
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#include <typeinfo>
#include <vector>
#include <map>
#include <string>
#include "Common.h"
namespace Engine
{
using std::vector;
using std::map;
using std::string;
template<typename T>
class MemoryPool
{
public:
MemoryPool();
~MemoryPool();
void* alloc(size_t size);
void dealloc(void* ptr);
private:
map<string, vector<char*>> _pool = map<string, vector<char*>>();
};
template<typename T>
MemoryPool<T>::MemoryPool()
{
}
template<typename T>
MemoryPool<T>::~MemoryPool()
{
}
template<typename T>
void* MemoryPool<T>::alloc(size_t size)
{
char* ptr;
vector<char*> specificPool;
std::map<string, vector<char*>>::const_iterator searcher = _pool.find(typeid(T).name());
if (searcher != _pool.end())
{
specificPool = searcher->second;
}
else
{
_pool.insert(std::pair<string, vector<char*>>(typeid(T).name(), vector<char*>()));
specificPool = _pool[typeid(T).name()];
}
// If there are idle pointers, then reuse one.
if (specificPool.size() > 0)
{
ptr = specificPool.back();
specificPool.pop_back();
return ptr;
}
// If there is nothing to reuse, then allocate new one.
ptr = new char[size];
if (ptr != NULL)
{
return ptr;
}
// Return nullptr when no memory left.
return nullptr;
}
template<typename T>
void MemoryPool<T>::dealloc(void* ptr)
{
std::map<string, vector<char*>>::const_iterator searcher = _pool.find(typeid(T).name());
if (searcher != _pool.end())
{
_pool[typeid(T).name()].push_back((char*)ptr);
}
else
{
cout << "ERROR: No pointer pointing at such object to deallocate!" << endl;
}
}
}
#endif
Object.h -> The parent class!
#ifndef OBJECT_H
#define OBJECT_H
#include <string>
#include <vector>
#include "MemoryPool.h"
namespace Engine
{
using std::string;
using std::vector;
template<typename T>
class Object
{
public:
static MemoryPool<T> memoryPool;
public:
Object(string name);
~Object();
string& getName();
void* operator new(size_t size);
void operator delete(void* ptr);
/**
* Reset the object. Always put all cleaners here instead of destructors!
*/
virtual void reset() = 0;
private:
string _name;
};
template <typename T>
MemoryPool<T> Object<T>::memoryPool = MemoryPool<T>();
template <typename T>
Object<T>::Object(string name) : _name(name)
{
}
template <typename T>
Object<T>::~Object()
{
_name.clear();
}
template <typename T>
void* Object<T>::operator new(size_t size)
{
return memoryPool.alloc(size);
}
template <typename T>
void Object<T>::operator delete(void* ptr)
{
memoryPool.dealloc(ptr);
}
template <typename T>
string& Object<T>::getName()
{
return _name;
}
}
#endif
Entity.h -> Sample usage of a child class that inherits Object.
#ifndef ENTITY_H
#define ENTITY_H
#include <vector>
#include "Common.h"
#include "Object.h"
#include "Component.h"
namespace Engine
{
using std::vector;
class Entity : public Object<Entity>
{
public:
vector<Component*> components;
Entity(string name);
~Entity();
void clearAllComponents();
void reset();
};
}
#endif
And here's how I benchmark them (without filling the pool first):
#include <iostream>
#include <ctime>
#include "Engine\Core\Entity.h"
using std::cout;
using std::endl;
using Engine::Entity;
/* Normal class */
class Car
{
public:
Car() {}
~Car() {}
void getName() {}
};
int main()
{
float startTime2 = (float)clock() / CLOCKS_PER_SEC;
for (int iii = 0; iii < 100000; ++iii)
{
Entity* o = new Entity("Entity");
}
cout << (float)clock() / CLOCKS_PER_SEC - startTime2 << endl;
float startTime = (float)clock() / CLOCKS_PER_SEC;
for (int i = 0; i < 100000; ++i)
{
Car* car = new Car();
}
cout << (float)clock() / CLOCKS_PER_SEC - startTime << endl;
return 0;
}
Note that there's Common.h header file. It contains only standard includes like cout and endl for debugging purposes, nothing else.
Thank you guys!