diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c1c223..2abf819 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,5 +4,6 @@ project(allocPool) set(CMAKE_CXX_STANDARD 23) add_executable(allocPool main.cpp - allocPool.cpp - allocPool.hpp) + allocPool.hpp + tests.cpp + tests.hpp) diff --git a/README.md b/README.md index 3838321..1b8bc33 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # allocPool - A simple & high performance object pool using modern C++ -This is allocPool, a pool of objects that allow you to avoid expensive allocations -during runtime. This preallocates objects in the constructor (with threads) then -offers you two functions: getPtr() and returnPtr(ptr). +This is allocPool, a pool of objects in a single header file that allow you to +avoid expensive allocations during runtime. This preallocates objects in the +constructor (with threads) then offers you two functions: `getPtr()` and `returnPtr(ptr)`. Using C++ concepts, we can use templates and require the class given to have a default constructor and to have a .reset() function. It will be used to clean the @@ -10,4 +10,5 @@ objects before giving them to another caller. This pool uses a hashmap and a pivot to make returnPtr(ptr) extremely fast. -It will automatically grow when the max capacity is reached. \ No newline at end of file +It will automatically grow when the max capacity is reached, though there will +be a performance penalty. \ No newline at end of file diff --git a/allocPool.cpp b/allocPool.cpp deleted file mode 100644 index 6e21ddd..0000000 --- a/allocPool.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "allocPool.hpp" - -template - requires std::default_initializable && resetable -allocPool::allocPool(size_t defaultAllocNumbers) - : vec(defaultAllocNumbers), pivot{defaultAllocNumbers} { - memset(&(vec[0]), 0, sizeof(vec[0]) * vec.size()); - initArray(defaultAllocNumbers); -} - -template - requires std::default_initializable && resetable -T *allocPool::getPtr() { - if (pivot == 0) - resizeVec(); - - auto *ptrToReturn{vec[0]}; - std::swap(vec[0], vec[pivot - 1]); - positionMap[vec[0]] = 0; - positionMap[vec[pivot - 1]] = pivot - 1; - pivot--; - return ptrToReturn; -} - -template - requires std::default_initializable && resetable -void allocPool::returnPtr(T *ptr) { - size_t pos = positionMap[ptr]; - vec[pos].reset(); - std::swap(vec[pos], vec[pivot]); - pivot++; -} - -template - requires std::default_initializable && resetable -void allocPool::initArray(size_t amount) { - const auto amountOfThreads{std::thread::hardware_concurrency()}; - assert(amountOfThreads); - const auto amountPerThreads{amount / amountOfThreads}; - - std::vector threads; - threads.reserve(amountOfThreads); - - for (size_t i{}; i < amountOfThreads; i++) - threads.emplace_back(&allocPool::initObjects, this, i, amountPerThreads); - - for (auto &t: threads) - t.join(); - - // Remainder - initObjects(vec[vec.size() - (amount % amountOfThreads)], amount % amountOfThreads); -} - -template - requires std::default_initializable && resetable -void allocPool::initObjects(size_t startIdx, size_t amount) { - for (size_t i{}; i < amount; i++) { - // TODO: Be more cache friendly by making a vector per thread, then doing memcpy into the original vector. - vec[startIdx + i] = new T; - positionMap[startIdx + i] = i; - } -} - -template - requires std::default_initializable && resetable -void allocPool::resizeVec() { - size_t size{vec.size()}; - vec.resize(2 * size); - memcpy(&(vec[size]), &(vec[0]), size); - initArray(size); -} \ No newline at end of file diff --git a/allocPool.hpp b/allocPool.hpp index 6ffc8c2..0c22a35 100644 --- a/allocPool.hpp +++ b/allocPool.hpp @@ -18,17 +18,73 @@ template requires std::default_initializable && resetable class allocPool { public: - explicit allocPool(size_t defaultAllocNumbers = 1000); + explicit allocPool(size_t defaultAllocNumbers = 1000) + : vec(defaultAllocNumbers), pivot{defaultAllocNumbers} { + memset(&(vec[0]), 0, sizeof(vec[0]) * vec.size()); + initArray(defaultAllocNumbers); + } - T *getPtr(); - void returnPtr(T *ptr); + T *getPtr() { + if (pivot == 0) + resizeVec(); + + auto *ptrToReturn{vec[0]}; + std::swap(vec[0], vec[pivot - 1]); + positionMap[vec[0]] = 0; + positionMap[vec[pivot - 1]] = pivot - 1; + pivot--; + return ptrToReturn; + } + + void returnPtr(T *ptr) { + size_t pos = positionMap[ptr]; + (vec[pos])->reset(); + std::swap(vec[pos], vec[pivot]); + positionMap[vec[pos]] = pos; + positionMap[vec[pivot]] = pivot; + pivot++; + } private: std::vector vec; std::unordered_map positionMap; size_t pivot; - void initArray(size_t amount); - void initObjects(size_t startIdx, size_t amount); - void resizeVec(); + void initArray(size_t amount) { + const auto amountOfThreads{std::thread::hardware_concurrency()}; + assert(amountOfThreads); + const auto amountPerThreads{amount / amountOfThreads}; + + std::vector threads; + threads.reserve(amountOfThreads); + + for (size_t i{}; i < amountOfThreads; i++) + threads.emplace_back(&allocPool::initObjects, this, i, amountPerThreads); + + for (auto &t: threads) + t.join(); + + // Remainder + initObjects(amount - (amount % amountOfThreads), amount % amountOfThreads); + } + + void initObjects(size_t startIdx, size_t amount) { + for (size_t i{}; i < amount; i++) { + // TODO: Be more cache friendly by making a vector per thread, then doing memcpy into the original vector. + vec[startIdx + i] = new T; + positionMap[vec[startIdx + i]] = i; + } + } + + void resizeVec() { + size_t size{vec.size()}; + vec.resize(2 * size); + pivot = size; + memcpy(&(vec[size]), &(vec[0]), sizeof(vec[0]) * size); + + for (size_t i{}; i < size; i++) + positionMap[vec[size + i]] = size + i; + + initArray(size); + } }; diff --git a/main.cpp b/main.cpp index 1ac82fd..e709d80 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,7 @@ -#include +#include "tests.hpp" int main() { + tests t; + t.runTests(); return 0; } diff --git a/tests.cpp b/tests.cpp new file mode 100644 index 0000000..7f774ec --- /dev/null +++ b/tests.cpp @@ -0,0 +1,27 @@ +#include "tests.hpp" + +// Variable statique de la classe tests. Elle doit être +// définie dans le fichier .cpp pour résoudre un problème +// de linker. +std::vector tests::vec; + +tests::tests() { + // Ajuster en fonction du nombre de tests. + vec.reserve(10); +} + +void tests::runTests() { + for (auto &i: vec) i(); +} + +ADD_TEST(allocPoolSimple) { + allocPool pool(2); + auto *var1{pool.getPtr()}; + auto *var2{pool.getPtr()}; + auto *var3{pool.getPtr()}; + pool.returnPtr(var2); + auto *var4{pool.getPtr()}; + pool.returnPtr(var1); + pool.returnPtr(var4); + pool.returnPtr(var3); +} \ No newline at end of file diff --git a/tests.hpp b/tests.hpp new file mode 100644 index 0000000..a774435 --- /dev/null +++ b/tests.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "allocPool.hpp" + +// On ajoute un pointeur de la fonction au vecteur vec. Cela permet de +// tout exécuter de façon propre. +// +// On utilise une struct avec un constructeur qui se fait appeler par +// défaut à sa construction. Lors de celle-ci, on met le pointeur dans +// le vecteur. +// +// Pour ajouter un test, simplement faire une déclaration de fonction +// avec ce macro dans le fichier Tests.cpp +#define ADD_TEST(name) \ + void name(); \ + struct name##_adder { \ + name##_adder() { \ + tests::vec.push_back(&name); \ + } \ + } name##_instance; \ + void name() + +class tests { +public: + tests(); + void runTests(); + static std::vector vec; +}; + +class stub { +public: + stub() = default; + void reset() {} +private: + int i = 15; +}; \ No newline at end of file