#pragma once
#include <atomic>
#include <cstddef>
#include <iostream>
#include <map>
#include <mutex>
#include <stdlib.h>
#include <unordered_map>
#include <utility>
#define THREADSAFE
namespace GENERIC_TS_POOL {
class MemPool {
struct AllocHeader {
size_t size;
size_t padding;
};
private:
void* _memory;
size_t _bytes;
size_t _freeSpace;
std::atomic<int> users = {0};
mutable std::mutex _mlock;
std::map<size_t, size_t> _freeBlocks;
std::unordered_map<size_t, AllocHeader> _allocBlocks;
float HWM = 0;
inline void _lock() const noexcept {
#ifdef THREADSAFE
_mlock.lock();
#endif
}
inline void _unlock() const noexcept {
#ifdef THREADSAFE
_mlock.unlock();
#endif
}
[[nodiscard]] inline size_t calculatePadding(size_t size, size_t alignment) const noexcept {
size_t remainder = size % alignment;
if (remainder == 0) {
return 0;
}
return alignment - remainder;
}
void reset() noexcept {
_lock();
_freeBlocks.clear();
_allocBlocks.clear();
_freeBlocks[reinterpret_cast<size_t>(_memory)] = capacity();
_freeSpace = capacity();
_unlock();
}
void reset_unsafe() noexcept {
_freeBlocks[reinterpret_cast<size_t>(_memory)] = capacity();
_freeSpace = capacity();
}
public:
MemPool(void* block, size_t maxSize) : _bytes(maxSize), _freeSpace(maxSize) {
if (block == nullptr) {
throw std::runtime_error("Null pointer cannot be used to instantiate mempool!");
}
_memory = block;
reset();
}
MemPool() : _memory(nullptr) {}
MemPool(const MemPool& other) = delete;
MemPool(MemPool&& other) = delete;
MemPool& operator=(const MemPool& other);
MemPool& operator=(MemPool&& other) = delete;
~MemPool() = default;
inline float memory_hwm(void) const noexcept { return HWM; }
inline size_t capacity(void) const noexcept { return _bytes; }
inline size_t size(void) const noexcept { return _bytes - _freeSpace; }
inline float load(void) const noexcept { return static_cast<float>(size()) / static_cast<float>(capacity()); }
inline void release(void) noexcept { reset(); }
void resize(void* block, size_t maxSize) {
if (block == nullptr) {
throw std::runtime_error("Null pointer cannot be used to resize mempool!");
}
_memory = block;
_bytes = maxSize;
_freeSpace = maxSize;
reset();
}
template <typename F>
void init(size_t maxSize, F&& allocFunction) {
users += 1;
_lock();
if (_memory != nullptr) {
_unlock();
return;
}
_memory = allocFunction(maxSize);
_bytes = maxSize;
_freeSpace = maxSize;
reset_unsafe();
_unlock();
}
void stats() noexcept {
printf("*********************************\n");
printf("Capacity=%zu , size= %zu , load = %f\n ", capacity(), size(), load());
printf("FreeSlots\n");
for (std::map<size_t, size_t>::iterator slot = _freeBlocks.begin(); slot != _freeBlocks.end(); slot++) {
std::cout << "\tBlock " << slot->first << " with size " << slot->second << std::endl;
}
printf("\n");
printf("\n");
printf("AllocSlots\n");
for (std::unordered_map<size_t, AllocHeader>::iterator slot = _allocBlocks.begin(); slot != _allocBlocks.end();
slot++) {
printf("\tBlock %zu with size = %zu and padding= %zu\n", slot->first, slot->second.size, slot->second.padding);
}
printf("*********************************\n");
}
void defrag() {
_lock();
auto left = _freeBlocks.begin();
while (left != _freeBlocks.end()) {
auto right = left;
right++;
if (right == _freeBlocks.end()) {
_unlock();
return;
}
if (left->first + left->second == right->first) {
std::pair<size_t, size_t> newBlock{left->first, left->second + right->second};
_freeBlocks.erase(left);
_freeBlocks.erase(right);
auto ok = _freeBlocks.insert(newBlock);
if (ok.second) {
left = ok.first;
} else {
throw std::runtime_error("Something caused a catastrophic failure during defragmentation");
}
} else {
left++;
}
}
_unlock();
return;
}
std::map<size_t, size_t>::iterator findBlock(size_t& bytes, size_t alignment) {
for (std::map<size_t, size_t>::iterator slot = _freeBlocks.begin(); slot != _freeBlocks.end(); slot++) {
size_t baseAddress = slot->first;
size_t padding = calculatePadding(baseAddress, alignment);
if (slot->second >= bytes + padding) {
bytes += padding;
return slot;
}
}
return _freeBlocks.end();
}
template <typename T>
[[nodiscard]] T* allocate(const size_t elements) noexcept {
if (elements == 0) {
return nullptr;
}
const size_t bytesToAllocate = elements * sizeof(T);
const size_t alignment = std::max(8ul, std::alignment_of<T>::value);
size_t allocationSize = bytesToAllocate;
if (bytesToAllocate > _freeSpace) {
std::cerr << "Not enough space to allocate " << bytesToAllocate << std::endl;
std::cerr << "Free space is " << _freeSpace << std::endl;
return nullptr;
}
_lock();
auto candidate = findBlock(allocationSize, alignment);
size_t pad = allocationSize - bytesToAllocate;
if (candidate == _freeBlocks.end()) {
_unlock();
return nullptr;
}
size_t baseAddress = candidate->first + pad;
_allocBlocks.insert(std::pair<size_t, AllocHeader>{baseAddress, AllocHeader{allocationSize, pad}});
_freeSpace -= allocationSize;
if (_freeSpace > 0) {
size_t newBlock = candidate->first + allocationSize;
size_t newBlockSize = candidate->second - allocationSize;
_freeBlocks.insert(std::pair<size_t, size_t>{newBlock, newBlockSize});
}
_freeBlocks.erase(candidate);
HWM = std::max(HWM, load());
_unlock();
return reinterpret_cast<T*>(baseAddress);
}
bool deallocate(void* ptr) noexcept {
if (ptr == nullptr) {
return true;
}
_lock();
auto it = _allocBlocks.find(reinterpret_cast<size_t>(ptr));
if (it == _allocBlocks.end()) {
_unlock();
std::cerr << "Could not find alloced block to deallocate!" << std::endl;
return false;
}
size_t pad = it->second.padding;
size_t baseAddress = it->first - pad;
_freeBlocks[baseAddress] = it->second.size;
_freeSpace += it->second.size;
if (it->second.size == 0) {
abort();
}
_allocBlocks.erase(it);
_unlock();
return true;
}
template <typename F>
void destroy_with(F&& f) {
if (users == 1) {
f(_memory);
_memory = nullptr;
}
users--;
}
};
}