dimanche 3 avril 2022

ObjectPool using unique_ptr implementation

I implement an ObjectPool using unique_ptr that allocates an array of T objects and uses 2 lists (free and busy) to handle the acquire() and release() in O(1). Here is the implementation:

`template <class T> class SimpleObjectPool { public: using pointer = std::unique_ptr<T,std::function<void(T*)> >; using PoolType = SimpleObjectPool<T>;

SimpleObjectPool(uint64_t size) : _poolSize(size) { allocate(); }
SimpleObjectPool(const PoolType& orig) = delete;
virtual ~SimpleObjectPool() {
    for(uint64_t i = 0; i < _poolIndex; i++)
        delete _poolVector[i];
}

int poolSize() const {
    return _poolSize * _poolIndex;
}

auto acquire() {
    std::lock_guard<std::recursive_mutex> guard(_mutex);

    if(_freeList.empty()) allocate();

    uint64_t freeIndex = getFreeObjectIndex();
    auto lastObjectItr = _busyList.end();
    lastObjectItr--;

    _poolVector[freeIndex/_poolSize]->_objects[freeIndex%_poolSize].clear();
    return std::move(pointer(&_poolVector[freeIndex/_poolSize]->_objects[freeIndex%_poolSize],
                             [this,lastObjectItr](T* element)->void{freeObject(element,lastObjectItr);}));
}

void release(pointer objectPtr) {
    std::lock_guard<std::recursive_mutex> guard(_mutex);
    objectPtr.reset();
}

int freeObjectSize() { return _freeList.size(); }
int busyObjectSize() { return _busyList.size(); }

private:

struct objectArray {
    objectArray(uint64_t size) {
        _objects = new T[size];
    }

    ~objectArray() {
        delete [] _objects;
    }

    T * _objects = nullptr;
};

void allocate() {
    std::lock_guard<std::recursive_mutex> guard(_mutex);
    std::cout << "allocate" << std::endl;
    _poolVector.push_back(new objectArray(_poolSize));
    for (uint64_t i = 0; i < _poolSize; i++) {
        uint64_t* index = new uint64_t(_poolIndex*_poolSize + i);
        _freeList.push_back(index);
    }
    _poolIndex++;
}

void freeObject(T* element, std::list<uint64_t*>::iterator iter) {
    std::lock_guard<std::recursive_mutex> guard(_mutex);
    _freeList.push_back(*iter);
    _busyList.erase(iter);
}

uint64_t getFreeObjectIndex(){
    uint64_t* index = _freeList.front();
    _freeList.pop_front();
    _busyList.push_back(index);
    return *index;
}

uint64_t _poolSize = 0;
std::list<uint64_t*> _freeList;
std::list<uint64_t*> _busyList;
std::vector<objectArray*> _poolVector;
uint64_t _poolIndex = 0;
std::recursive_mutex _mutex;

}; `

But the problem is that the line return std::move(pointer(&_poolVector[freeIndex/_poolSize]->_objects[freeIndex%_poolSize], [this,lastObjectItr](T* element)->void{freeObject(element,lastObjectItr);}));

actually calls to new() so the whole point of using pre-allocated object is missed. Is there a way to overcome this behavior ?

`class TestFile { public: TestFile(){ clear(); //std::cout<<"Construct default TestFile"<<*this<<"\n"; }

~TestFile() {
    //std::cout<<"Delete TestFile"<<*this<<"\n";
}

void clear(){
    _fileId = -1;
    _fileName.clear();
}

friend std::ostream& operator<<(std::ostream& ostream, const TestFile& test) {
    ostream<<"["<<test._fileId<<",'"<<test._fileName<<"']";
    return ostream;
}

void setFileId(int id) { _fileId = id; }
void setFileName(std::string name) { _fileName = name; }

private: int _fileId; std::string _fileName; };

using TestPool = SimpleObjectPool<TestFile>;

bool simpleTest(int size) {

TestPool pool(size);

if(pool.freeObjectSize() != size) return false;
if(pool.busyObjectSize() != 0) return false;

auto pt1 = pool.acquire();
pt1->setFileId(1234);
pt1->setFileName("TestFileName");

if(pool.freeObjectSize() != size-1) return false;
if(pool.busyObjectSize() != 1) return false;

pool.release(std::move(pt1));
if(pt1 != nullptr) return false;

if(pool.freeObjectSize() != size) return false;
if(pool.busyObjectSize() != 0) return false;

return true;
}`

Aucun commentaire:

Enregistrer un commentaire