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)
|
||||
|
||||
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++
|
||||
|
||||
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.
|
||||
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>
|
||||
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<T *> vec;
|
||||
std::unordered_map<T *, size_t> 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<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() {
|
||||
tests t;
|
||||
t.runTests();
|
||||
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