allocPool: Add tests and fix numerous bugs.
This commit is contained in:
parent
0af20178b8
commit
dead668fb7
@ -4,5 +4,6 @@ project(allocPool)
|
|||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
add_executable(allocPool main.cpp
|
add_executable(allocPool main.cpp
|
||||||
allocPool.cpp
|
allocPool.hpp
|
||||||
allocPool.hpp)
|
tests.cpp
|
||||||
|
tests.hpp)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# allocPool - A simple & high performance object pool using modern C++
|
# allocPool - A simple & high performance object pool using modern C++
|
||||||
|
|
||||||
This is allocPool, a pool of objects that allow you to avoid expensive allocations
|
This is allocPool, a pool of objects in a single header file that allow you to
|
||||||
during runtime. This preallocates objects in the constructor (with threads) then
|
avoid expensive allocations during runtime. This preallocates objects in the
|
||||||
offers you two functions: getPtr() and returnPtr(ptr).
|
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
|
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
|
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.
|
This pool uses a hashmap and a pivot to make returnPtr(ptr) extremely fast.
|
||||||
|
|
||||||
It will automatically grow when the max capacity is reached.
|
It will automatically grow when the max capacity is reached, though there will
|
||||||
|
be a performance penalty.
|
@ -1,71 +0,0 @@
|
|||||||
#include "allocPool.hpp"
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
allocPool<T>::allocPool(size_t defaultAllocNumbers)
|
|
||||||
: vec(defaultAllocNumbers), pivot{defaultAllocNumbers} {
|
|
||||||
memset(&(vec[0]), 0, sizeof(vec[0]) * vec.size());
|
|
||||||
initArray(defaultAllocNumbers);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
T *allocPool<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
void allocPool<T>::returnPtr(T *ptr) {
|
|
||||||
size_t pos = positionMap[ptr];
|
|
||||||
vec[pos].reset();
|
|
||||||
std::swap(vec[pos], vec[pivot]);
|
|
||||||
pivot++;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
void allocPool<T>::initArray(size_t amount) {
|
|
||||||
const auto amountOfThreads{std::thread::hardware_concurrency()};
|
|
||||||
assert(amountOfThreads);
|
|
||||||
const auto amountPerThreads{amount / amountOfThreads};
|
|
||||||
|
|
||||||
std::vector<std::thread> 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<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
void allocPool<T>::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<class T>
|
|
||||||
requires std::default_initializable<T> && resetable<T>
|
|
||||||
void allocPool<T>::resizeVec() {
|
|
||||||
size_t size{vec.size()};
|
|
||||||
vec.resize(2 * size);
|
|
||||||
memcpy(&(vec[size]), &(vec[0]), size);
|
|
||||||
initArray(size);
|
|
||||||
}
|
|
@ -18,17 +18,73 @@ template<class T>
|
|||||||
requires std::default_initializable<T> && resetable<T>
|
requires std::default_initializable<T> && resetable<T>
|
||||||
class allocPool {
|
class allocPool {
|
||||||
public:
|
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();
|
T *getPtr() {
|
||||||
void returnPtr(T *ptr);
|
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:
|
private:
|
||||||
std::vector<T *> vec;
|
std::vector<T *> vec;
|
||||||
std::unordered_map<T *, size_t> positionMap;
|
std::unordered_map<T *, size_t> positionMap;
|
||||||
size_t pivot;
|
size_t pivot;
|
||||||
|
|
||||||
void initArray(size_t amount);
|
void initArray(size_t amount) {
|
||||||
void initObjects(size_t startIdx, size_t amount);
|
const auto amountOfThreads{std::thread::hardware_concurrency()};
|
||||||
void resizeVec();
|
assert(amountOfThreads);
|
||||||
|
const auto amountPerThreads{amount / amountOfThreads};
|
||||||
|
|
||||||
|
std::vector<std::thread> 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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
4
main.cpp
4
main.cpp
@ -1,5 +1,7 @@
|
|||||||
#include <iostream>
|
#include "tests.hpp"
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
tests t;
|
||||||
|
t.runTests();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
27
tests.cpp
Normal file
27
tests.cpp
Normal file
@ -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<void (*)()> 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<stub> 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);
|
||||||
|
}
|
39
tests.hpp
Normal file
39
tests.hpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<void (*)()> vec;
|
||||||
|
};
|
||||||
|
|
||||||
|
class stub {
|
||||||
|
public:
|
||||||
|
stub() = default;
|
||||||
|
void reset() {}
|
||||||
|
private:
|
||||||
|
int i = 15;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user