From 4cf9269806f51d93ddb8031916130feb47c5355a Mon Sep 17 00:00:00 2001 From: Bert Melis Date: Fri, 1 Mar 2024 11:18:20 +0100 Subject: [PATCH] Add fixed blocksize pool --- src/Fixed.h | 107 ++++++++ src/MemoryPool.h | 220 +---------------- src/Variable.h | 228 ++++++++++++++++++ test/test_FixInt/test_FixInt.cpp | 109 +++++++++ .../test_VarInt.cpp} | 0 .../test_VarStruct.cpp} | 0 6 files changed, 446 insertions(+), 218 deletions(-) create mode 100644 src/Fixed.h create mode 100644 src/Variable.h create mode 100644 test/test_FixInt/test_FixInt.cpp rename test/{test_Int/test_Int.cpp => test_VarInt/test_VarInt.cpp} (100%) rename test/{test_Struct/test_Struct.cpp => test_VarStruct/test_VarStruct.cpp} (100%) diff --git a/src/Fixed.h b/src/Fixed.h new file mode 100644 index 0000000..c1e7581 --- /dev/null +++ b/src/Fixed.h @@ -0,0 +1,107 @@ +/* +Copyright (c) 2024 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include // std::size_t +#include // assert +#include // NOLINT [build/c++11] std::mutex, std::lock_guard + +#ifdef MEMPOL_DEBUG +#include +#endif + +namespace MemoryPool { + +template +class Fixed { + public: + Fixed() + : _buffer{0} + , _head(_buffer) { + unsigned char* b = _head; + std::size_t adjustedBlocksize = sizeof(sizeof(unsigned char*)) > sizeof(blocksize) ? sizeof(sizeof(unsigned char*)) : sizeof(blocksize); + for (std::size_t i = 0; i < nrBlocks - 1; ++i) { + *reinterpret_cast(b) = b + adjustedBlocksize; + b += adjustedBlocksize; + } + *reinterpret_cast(b) = nullptr; + } + + // no copy nor move + Fixed (const Fixed&) = delete; + Fixed& operator= (const Fixed&) = delete; + + void* malloc() { + const std::lock_guard lockGuard(_mutex); + if (_head) { + void* retVal = _head; + _head = *reinterpret_cast(_head); + return retVal; + } + return nullptr; + } + + void free(void* ptr) { + if (!ptr) return; + const std::lock_guard lockGuard(_mutex); + *reinterpret_cast(ptr) = _head; + _head = reinterpret_cast(ptr); + } + + std::size_t freeMemory() { + const std::lock_guard lockGuard(_mutex); + unsigned char* i = _head; + std::size_t retVal = 0; + while (i) { + retVal += blocksize; + i = reinterpret_cast(i)[0]; + } + return retVal; + } + + #ifdef MEMPOL_DEBUG + void print() { + std::size_t adjustedBlocksize = sizeof(sizeof(unsigned char*)) > sizeof(blocksize) ? sizeof(sizeof(unsigned char*)) : sizeof(blocksize); + std::cout << "+--------------------" << std::endl; + std::cout << "|start:" << reinterpret_cast(_buffer) << std::endl; + std::cout << "|blocks:" << nrBlocks << std::endl; + std::cout << "|blocksize:" << adjustedBlocksize << std::endl; + std::cout << "|head: " << reinterpret_cast(_head) << std::endl; + unsigned char* currentBlock = _buffer; + + for (std::size_t i = 0; i < nrBlocks; ++i) { + std::cout << "|" << i + 1 << ": " << reinterpret_cast(currentBlock) << std::endl; + if (_isFree(currentBlock)) { + std::cout << "| free" << std::endl; + std::cout << "| next: " << reinterpret_cast(*reinterpret_cast(currentBlock)) << std::endl; + } else { + std::cout << "| allocated" << std::endl; + } + currentBlock += adjustedBlocksize; + } + std::cout << "+--------------------" << std::endl; + } + + bool _isFree(unsigned char* ptr) { + unsigned char* b = _head; + while (b) { + if (b == ptr) return true; + b = *reinterpret_cast(b); + } + return false; + } + #endif + + private: + unsigned char _buffer[nrBlocks * (sizeof(sizeof(unsigned char*)) > sizeof(blocksize) ? sizeof(sizeof(unsigned char*)) : sizeof(blocksize))]; + unsigned char* _head; + std::mutex _mutex; +}; + +} // end namespace MemoryPool diff --git a/src/MemoryPool.h b/src/MemoryPool.h index 53225bf..5b198ea 100644 --- a/src/MemoryPool.h +++ b/src/MemoryPool.h @@ -8,221 +8,5 @@ the LICENSE file. #pragma once -#include // std::size_t -#include // assert -#include // NOLINT [build/c++11] std::mutex, std::lock_guard - -#ifdef MEMPOL_DEBUG -#include -#endif - -namespace MemoryPool { - -template -class Variable { - public: - Variable() - : _buffer{0} - , _head(nullptr) - #ifdef MEMPOL_DEBUG - , _bufferSize(0) - #endif - { - std::size_t _normBlocksize = blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0); - size_t nrBlocksToAlloc = nrBlocks * (_normBlocksize + 1); - BlockHeader* h = reinterpret_cast(_buffer); - h->next = nullptr; - h->size = nrBlocksToAlloc; - _head = h; - - #ifdef MEMPOL_DEBUG - _bufferSize = nrBlocksToAlloc; - #endif - } - - // no copy nor move - Variable (const Variable&) = delete; - Variable& operator= (const Variable&) = delete; - - void* malloc(size_t size) { - const std::lock_guard lockGuard(_mutex); - if (size == 0) return nullptr; - - size = (size / sizeof(BlockHeader) + (size % sizeof(BlockHeader) != 0)) + 1; // count by BlockHeader size, add 1 for header - - #ifdef MEMPOL_DEBUG - std::cout << "malloc (raw) " << size << std::endl; - std::cout << "malloc (adj) " << size << " - "; - #endif - - BlockHeader* currentBlock = _head; - BlockHeader* previousBlock = nullptr; - void* retVal = nullptr; - - // iterate through linked free blocks - while (currentBlock) { - // consume whole block is size equals required size - if (currentBlock->size == size) { - if (previousBlock) previousBlock->next = currentBlock->next; - break; - - // split block if size is larger and add second part to list of free blocks - } else if (currentBlock->size > size) { - BlockHeader* newBlock = currentBlock + size; - if (previousBlock) previousBlock->next = newBlock; - newBlock->next = currentBlock->next; - newBlock->size = currentBlock->size - size; - currentBlock->next = newBlock; - break; - } - previousBlock = currentBlock; - currentBlock = currentBlock->next; - } - - if (currentBlock) { - if (currentBlock == _head) { - _head = currentBlock->next; - } - currentBlock->size = size; - currentBlock->next = nullptr; // used when freeing memory - retVal = currentBlock + 1; - #ifdef MEMPOL_DEBUG - std::cout << "ok" << std::endl; - #endif - } else { - #ifdef MEMPOL_DEBUG - std::cout << "nok" << std::endl; - #endif - (void)0; - } - - return retVal; - } - - void free(void* ptr) { - if (!ptr) return; - // check if ptr points to region in _buffer - - #ifdef MEMPOL_DEBUG - std::cout << "free " << static_cast(reinterpret_cast(ptr) - 1) << std::endl; - #endif - - const std::lock_guard lockGuard(_mutex); - - BlockHeader* toFree = reinterpret_cast(ptr) - 1; - BlockHeader* previous = reinterpret_cast(_buffer); - BlockHeader* next = _head; - - // toFree is the only free block - if (!next) { - _head = toFree; - return; - } - - while (previous) { - if (!next || toFree < next) { - // 1. add block to linked list of free blocks - if (toFree < _head) { - toFree->next = _head; - _head = toFree; - } else { - previous->next = toFree; - toFree->next = next; - } - - // 2. merge with previous if adjacent - if (toFree > _head && toFree == previous + previous->size) { - previous->size += toFree->size; - previous->next = toFree->next; - toFree = previous; // used in next check - } - - // 3. merge with next if adjacent - if (toFree + toFree->size == next) { - toFree->size += next->size; - toFree->next = next->next; - } - - // 4. done - return; - } - previous = next; - next = next->next; - } - } - - std::size_t freeMemory() { - const std::lock_guard lockGuard(_mutex); - size_t retVal = 0; - BlockHeader* currentBlock = reinterpret_cast(_head); - - while (currentBlock) { - retVal += currentBlock->size - 1; - currentBlock = currentBlock->next; - } - - return retVal * sizeof(BlockHeader); - } - - std::size_t maxBlockSize() { - const std::lock_guard lockGuard(_mutex); - size_t retVal = 0; - BlockHeader* currentBlock = reinterpret_cast(_head); - - while (currentBlock) { - retVal = std::max(currentBlock->size - 1, retVal); - currentBlock = currentBlock->next; - } - - return retVal * sizeof(BlockHeader); - } - - #ifdef MEMPOL_DEBUG - void print() { - std::cout << "+--------------------" << std::endl; - std::cout << "|start:" << static_cast(_buffer) << std::endl; - std::cout << "|size:" << _bufferSize << std::endl; - std::cout << "|headersize:" << sizeof(BlockHeader) << std::endl; - std::cout << "|head: " << static_cast(_head) << std::endl; - BlockHeader* nextFreeBlock = _head; - BlockHeader* currentBlock = reinterpret_cast(_buffer); - size_t blockNumber = 1; - while (currentBlock < reinterpret_cast(_buffer) + _bufferSize) { - std::cout << "|" << blockNumber << ": " << static_cast(currentBlock) << std::endl; - std::cout << "| " << static_cast(currentBlock->next) << std::endl; - std::cout << "| " << currentBlock->size << std::endl; - if (currentBlock == nextFreeBlock) { - std::cout << "| free" << std::endl; - nextFreeBlock = nextFreeBlock->next; - } else { - std::cout << "| allocated" << std::endl; - } - ++blockNumber; - currentBlock += currentBlock->size; - } - std::cout << "+--------------------" << std::endl; - } - #endif - - private: - struct BlockHeader { - BlockHeader* next; - std::size_t size; - }; - /* - pool size is aligned to sizeof(BlockHeader). - requested blocksize is therefore multiple of blockheader (rounded up) - total size = nr requested blocks * multiplier * blockheadersize - - see constructor for calculation - */ - unsigned char _buffer[(nrBlocks * ((blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0)) + 1)) * sizeof(BlockHeader)]; - BlockHeader* _head; - std::mutex _mutex; - - #ifdef MEMPOL_DEBUG - std::size_t _bufferSize; - #endif -}; - -} // end namespace MemoryPool +#include "Variable.h" +#include "Fixed.h" diff --git a/src/Variable.h b/src/Variable.h new file mode 100644 index 0000000..0f490c7 --- /dev/null +++ b/src/Variable.h @@ -0,0 +1,228 @@ +/* +Copyright (c) 2024 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#pragma once + +#include // std::size_t +#include // assert +#include // NOLINT [build/c++11] std::mutex, std::lock_guard + +#ifdef MEMPOL_DEBUG +#include +#endif + +namespace MemoryPool { + +template +class Variable { + public: + Variable() + : _buffer{0} + , _head(nullptr) + #ifdef MEMPOL_DEBUG + , _bufferSize(0) + #endif + { + std::size_t _normBlocksize = blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0); + size_t nrBlocksToAlloc = nrBlocks * (_normBlocksize + 1); + BlockHeader* h = reinterpret_cast(_buffer); + h->next = nullptr; + h->size = nrBlocksToAlloc; + _head = h; + + #ifdef MEMPOL_DEBUG + _bufferSize = nrBlocksToAlloc; + #endif + } + + // no copy nor move + Variable (const Variable&) = delete; + Variable& operator= (const Variable&) = delete; + + void* malloc(size_t size) { + const std::lock_guard lockGuard(_mutex); + if (size == 0) return nullptr; + + size = (size / sizeof(BlockHeader) + (size % sizeof(BlockHeader) != 0)) + 1; // count by BlockHeader size, add 1 for header + + #ifdef MEMPOL_DEBUG + std::cout << "malloc (raw) " << size << std::endl; + std::cout << "malloc (adj) " << size << " - "; + #endif + + BlockHeader* currentBlock = _head; + BlockHeader* previousBlock = nullptr; + void* retVal = nullptr; + + // iterate through linked free blocks + while (currentBlock) { + // consume whole block is size equals required size + if (currentBlock->size == size) { + if (previousBlock) previousBlock->next = currentBlock->next; + break; + + // split block if size is larger and add second part to list of free blocks + } else if (currentBlock->size > size) { + BlockHeader* newBlock = currentBlock + size; + if (previousBlock) previousBlock->next = newBlock; + newBlock->next = currentBlock->next; + newBlock->size = currentBlock->size - size; + currentBlock->next = newBlock; + break; + } + previousBlock = currentBlock; + currentBlock = currentBlock->next; + } + + if (currentBlock) { + if (currentBlock == _head) { + _head = currentBlock->next; + } + currentBlock->size = size; + currentBlock->next = nullptr; // used when freeing memory + retVal = currentBlock + 1; + #ifdef MEMPOL_DEBUG + std::cout << "ok" << std::endl; + #endif + } else { + #ifdef MEMPOL_DEBUG + std::cout << "nok" << std::endl; + #endif + (void)0; + } + + return retVal; + } + + void free(void* ptr) { + if (!ptr) return; + // check if ptr points to region in _buffer + + #ifdef MEMPOL_DEBUG + std::cout << "free " << static_cast(reinterpret_cast(ptr) - 1) << std::endl; + #endif + + const std::lock_guard lockGuard(_mutex); + + BlockHeader* toFree = reinterpret_cast(ptr) - 1; + BlockHeader* previous = reinterpret_cast(_buffer); + BlockHeader* next = _head; + + // toFree is the only free block + if (!next) { + _head = toFree; + return; + } + + while (previous) { + if (!next || toFree < next) { + // 1. add block to linked list of free blocks + if (toFree < _head) { + toFree->next = _head; + _head = toFree; + } else { + previous->next = toFree; + toFree->next = next; + } + + // 2. merge with previous if adjacent + if (toFree > _head && toFree == previous + previous->size) { + previous->size += toFree->size; + previous->next = toFree->next; + toFree = previous; // used in next check + } + + // 3. merge with next if adjacent + if (toFree + toFree->size == next) { + toFree->size += next->size; + toFree->next = next->next; + } + + // 4. done + return; + } + previous = next; + next = next->next; + } + } + + std::size_t freeMemory() { + const std::lock_guard lockGuard(_mutex); + size_t retVal = 0; + BlockHeader* currentBlock = reinterpret_cast(_head); + + while (currentBlock) { + retVal += currentBlock->size - 1; + currentBlock = currentBlock->next; + } + + return retVal * sizeof(BlockHeader); + } + + std::size_t maxBlockSize() { + const std::lock_guard lockGuard(_mutex); + size_t retVal = 0; + BlockHeader* currentBlock = reinterpret_cast(_head); + + while (currentBlock) { + retVal = (currentBlock->size - 1 > retVal) ? currentBlock->size - 1 : retVal; + currentBlock = currentBlock->next; + } + + return retVal * sizeof(BlockHeader); + } + + #ifdef MEMPOL_DEBUG + void print() { + std::cout << "+--------------------" << std::endl; + std::cout << "|start:" << static_cast(_buffer) << std::endl; + std::cout << "|size:" << _bufferSize << std::endl; + std::cout << "|headersize:" << sizeof(BlockHeader) << std::endl; + std::cout << "|head: " << static_cast(_head) << std::endl; + BlockHeader* nextFreeBlock = _head; + BlockHeader* currentBlock = reinterpret_cast(_buffer); + size_t blockNumber = 1; + while (currentBlock < reinterpret_cast(_buffer) + _bufferSize) { + std::cout << "|" << blockNumber << ": " << static_cast(currentBlock) << std::endl; + std::cout << "| " << static_cast(currentBlock->next) << std::endl; + std::cout << "| " << currentBlock->size << std::endl; + if (currentBlock == nextFreeBlock) { + std::cout << "| free" << std::endl; + nextFreeBlock = nextFreeBlock->next; + } else { + std::cout << "| allocated" << std::endl; + } + ++blockNumber; + currentBlock += currentBlock->size; + } + std::cout << "+--------------------" << std::endl; + } + #endif + + private: + struct BlockHeader { + BlockHeader* next; + std::size_t size; + }; + /* + pool size is aligned to sizeof(BlockHeader). + requested blocksize is therefore multiple of blockheader (rounded up) + total size = nr requested blocks * multiplier * blockheadersize + + see constructor for calculation + */ + unsigned char _buffer[(nrBlocks * ((blocksize / sizeof(BlockHeader) + ((blocksize % sizeof(BlockHeader)) ? 1 : 0)) + 1)) * sizeof(BlockHeader)]; + BlockHeader* _head; + std::mutex _mutex; + + #ifdef MEMPOL_DEBUG + std::size_t _bufferSize; + #endif +}; + +} // end namespace MemoryPool diff --git a/test/test_FixInt/test_FixInt.cpp b/test/test_FixInt/test_FixInt.cpp new file mode 100644 index 0000000..f578f3e --- /dev/null +++ b/test/test_FixInt/test_FixInt.cpp @@ -0,0 +1,109 @@ +/* +Copyright (c) 2024 Bert Melis. All rights reserved. + +This work is licensed under the terms of the MIT license. +For a copy, see or +the LICENSE file. +*/ + +#include + +#include + +void setUp() {} +void tearDown() {} + +void emptyPool() { + const size_t nrBlocks = 3; + const size_t blocksize = sizeof(int); + MemoryPool::Fixed pool; + + pool.print(); + + TEST_ASSERT_EQUAL_UINT(nrBlocks * blocksize, pool.freeMemory()); +} + +void mallocFull() { + const size_t nrBlocks = 3; + const size_t blocksize = sizeof(int); + MemoryPool::Fixed pool; + + int* int1 = reinterpret_cast(pool.malloc()); + int* int2 = reinterpret_cast(pool.malloc()); + int* int3 = reinterpret_cast(pool.malloc()); + int* int4 = reinterpret_cast(pool.malloc()); + + pool.print(); + + TEST_ASSERT_NOT_NULL(int1); + TEST_ASSERT_NOT_NULL(int2); + TEST_ASSERT_NOT_NULL(int3); + TEST_ASSERT_NULL(int4); + + *int1 = 1; + *int2 = 2; + *int3 = 3; + TEST_ASSERT_EQUAL_INT(1, *int1); + TEST_ASSERT_EQUAL_INT(2, *int2); + TEST_ASSERT_EQUAL_INT(3, *int3); + TEST_ASSERT_EQUAL_UINT(0, pool.freeMemory()); +} + +void freePartial() { + const size_t nrBlocks = 4; + const size_t blocksize = sizeof(int); + MemoryPool::Fixed pool; + + int* int1 = reinterpret_cast(pool.malloc()); + int* int2 = reinterpret_cast(pool.malloc()); + int* int3 = reinterpret_cast(pool.malloc()); + int* int4 = reinterpret_cast(pool.malloc()); + int* int5 = reinterpret_cast(pool.malloc()); + pool.print(); + + (void) int1; + (void) int3; + pool.free(int2); + pool.print(); + TEST_ASSERT_EQUAL_UINT(1 * blocksize, pool.freeMemory()); + pool.free(int4); + pool.print(); + TEST_ASSERT_EQUAL_UINT(2 * blocksize, pool.freeMemory()); + int5 = reinterpret_cast(pool.malloc()); + TEST_ASSERT_NOT_NULL(int5); + pool.print(); + + TEST_ASSERT_EQUAL_UINT(1 * blocksize, pool.freeMemory()); +} + +void freeEmpty() { + const size_t nrBlocks = 4; + const size_t blocksize = sizeof(int); + MemoryPool::Fixed pool; + + int* int1 = reinterpret_cast(pool.malloc()); + int* int2 = reinterpret_cast(pool.malloc()); + int* int3 = reinterpret_cast(pool.malloc()); + int* int4 = reinterpret_cast(pool.malloc()); + + pool.print(); + pool.free(int1); + pool.print(); + pool.free(int2); + pool.print(); + pool.free(int3); + pool.print(); + pool.free(int4); + pool.print(); + + TEST_ASSERT_EQUAL_UINT(nrBlocks * blocksize, pool.freeMemory()); +} + +int main() { + UNITY_BEGIN(); + RUN_TEST(emptyPool); + RUN_TEST(mallocFull); + RUN_TEST(freePartial); + RUN_TEST(freeEmpty); + return UNITY_END(); +} diff --git a/test/test_Int/test_Int.cpp b/test/test_VarInt/test_VarInt.cpp similarity index 100% rename from test/test_Int/test_Int.cpp rename to test/test_VarInt/test_VarInt.cpp diff --git a/test/test_Struct/test_Struct.cpp b/test/test_VarStruct/test_VarStruct.cpp similarity index 100% rename from test/test_Struct/test_Struct.cpp rename to test/test_VarStruct/test_VarStruct.cpp