This commit is contained in:
Ilusha 2023-05-28 01:16:30 +03:00 committed by IlushaShurupov
parent d4c558a59a
commit db05d963be
74 changed files with 4473 additions and 3231 deletions

20
Allocators/CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.2)
set(CMAKE_CXX_STANDARD 23)
project(Allocator)
### ---------------------- Static Library --------------------- ###
file(GLOB SOURCES "./private/*.cpp")
add_library(${PROJECT_NAME} STATIC ${SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC ./public/)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)
### -------------------------- Tests -------------------------- ###
enable_testing()
add_executable(${PROJECT_NAME}Tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Tests.cpp)
target_link_libraries(${PROJECT_NAME}Tests ${PROJECT_NAME})
add_test(${PROJECT_NAME} ${PROJECT_NAME}Tests)
install(TARGETS ${PROJECT_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}/lib)

42
Allocators/README.md Normal file
View file

@ -0,0 +1,42 @@
# Profiling
## Memory Leaks
Example program with memory leaks:
```c++
#include "allocators.h"
void test_call22() { new int; }
void test_call21() { new float; }
void test_call11() {
test_call21();
test_call22();
}
int main(char argc, char* argv[]) {
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL };
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
TestModule.initialize();
test_call11();
TestModule.deinitialize();
}
```
If memory leaks were detected it will be loged in the output console.
![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png)
Also debug.memleaks binary will be generated in the working directory that can be viewved with MemLeaks Viewver.
![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png)
## Memory Usage Analisys
Currently outdated
## Benchmarks
Currently outdated

View file

@ -0,0 +1,21 @@
#include "Allocators.hpp"
static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleBase, NULL };
tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies);
void* operator new(size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
void* operator new[](size_t aSize) { return tp::HeapAllocGlobal::allocate(aSize); }
void operator delete(void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
void operator delete[](void* aPtr) { tp::HeapAllocGlobal::deallocate(aPtr); }
void* operator new(size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); }
void* operator new[](size_t aSize, tp::HeapAlloc& aAlloc) { return aAlloc.allocate(aSize); }
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); }
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc) { aAlloc.deallocate(aPtr); }
void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); }
void* operator new[](size_t aSize, tp::HeapAllocGlobal& aAlloc) { return aAlloc.allocate(aSize); }
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); }
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc) { aAlloc.deallocate(aPtr); }

View file

@ -0,0 +1,114 @@
/*
*
* Implementation uses embedded one-directional linked list to track free blocks.
* The embedded part ensures that there is no memory overhead on block specifically.
* Linked list is initialized iteratively on each allocation if it has not been already.
* Allocating:
* 1) updating list entry to stored in the entry itself next free pointer
* 2) returning entry before (1).
*
* Deallocating:
* 1) assigning list entry value to the deleted block
* 2) updating list entry to that block.
*
*/
#include "ChunkAllocator.hpp"
#include "PrivateConfig.hpp"
#include "HeapAllocatorGlobal.hpp"
using namespace tp;
enum : ualni {
ALIGNED_SIZE = ENV_ALNI_SIZE_B,
WRAP_SIZE_ALN = MEM_WRAP_SIZE,
WRAP_SIZE = WRAP_SIZE_ALN * ALIGNED_SIZE,
WRAP_VAL = MEM_WRAP_FILL_VAL,
CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL,
CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL,
};
ChunkAlloc::ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize) {
auto const temp = aBlockSize / ALIGNED_SIZE;
mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2;
mBuff = (ualni*) aMemory;
mNBlocks = (aMemSize / ALIGNED_SIZE) / mBSize;
mNFreeBlocks = mNBlocks;
}
ChunkAlloc::ChunkAlloc(ualni aBlockSize, ualni aNBlocks) {
auto const temp = aBlockSize / ALIGNED_SIZE;
mBSize = ((aBlockSize % ALIGNED_SIZE) ? temp + 1 : temp) + WRAP_SIZE_ALN * 2;
mNBlocks = aNBlocks;
mNFreeBlocks = mNBlocks;
mBuff = (ualni*)HeapAllocGlobal::allocate(mNBlocks * mBSize * ALIGNED_SIZE);
mOwnBuff = true;
}
void* ChunkAlloc::allocate() {
ASSERT(mNFreeBlocks && "Out Of Memory");
// 1) PreInitialize blocks
if (mNInitBlocks < mNBlocks) {
*(mBuff + mNInitBlocks * mBSize) = (ualni)(mBuff + (mNInitBlocks++) * mBSize);
}
// 2) Find free block and update next free block
auto data = mNextBlock;
mNextBlock = (ualni*)(*data);
mNFreeBlocks--;
#ifdef MEM_DEBUG
// 3) Fill Wrap and offset data
auto wrap_top = data;
data += WRAP_SIZE_ALN;
auto wrap_bottom = data + mBSize;
memsetv(wrap_top, WRAP_SIZE, WRAP_VAL);
memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL);
// 4) Clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, mBSize * ALIGNED_SIZE, CLEAR_ALLOC_VAL);
#endif
#endif
return data;
}
void ChunkAlloc::deallocate(void* aPtr) {
auto block = (ualni*)aPtr;
#ifdef MEM_DEBUG
// 3) Check Wrap and offset data
auto wrap_bottom = block + mBSize;
auto wrap_top = block - WRAP_SIZE_ALN;
block = wrap_top;
// 3) Check the wrap
RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!");
// 4) Clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(aPtr, mBSize * ALIGNED_SIZE, CLEAR_DEALLOC_VAL);
#endif
#endif
(*block) = (ualni)mNextBlock;
mNextBlock = block;
mNFreeBlocks++;
}
bool ChunkAlloc::isFull() const { return !mNFreeBlocks; }
bool ChunkAlloc::isEmpty() const { return mNFreeBlocks == mNBlocks; }
ChunkAlloc::~ChunkAlloc() {
// TODO : check for leaks
if (mOwnBuff) {
HeapAllocGlobal::deallocate(mBuff);
}
}

View file

@ -0,0 +1,78 @@
#include "HeapAllocator.hpp"
#include "HeapAllocatorGlobal.hpp"
#include "PrivateConfig.hpp"
#include <stddef.h>
#include <cstdlib>
using namespace tp;
#if not defined(MEM_DEBUG)
// ----------------------- Release Implementation ---------------------------- //
void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
void HeapAlloc::deallocate(void* aPtr) { free(aPtr); }
HeapAlloc::~HeapAlloc() {}
#else
namespace tp {
struct MemHeadLocal {
MemHeadLocal* mPrev;
MemHeadLocal* mNext;
};
};
void* HeapAlloc::allocate(ualni aBlockSize) {
auto head = (MemHeadLocal*) HeapAllocGlobal::allocate(aBlockSize + sizeof(MemHeadLocal));
auto out = head + 1;
mNumAllocations++;
if (mEntry) {
head->mNext = mEntry->mNext;
head->mPrev = mEntry->mPrev;
if (mEntry->mNext) mEntry->mNext->mPrev = head;
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
}
else {
head->mNext = nullptr;
head->mPrev = nullptr;
}
mEntry = head;
return out;
}
void HeapAlloc::deallocate(void* aPtr) {
auto head = ((MemHeadLocal*)(aPtr)) - 1;
mNumAllocations--;
if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev;
if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext;
if (head == mEntry) {
if (mEntry->mNext) {
mEntry = mEntry->mNext;
}
else {
mEntry = mEntry->mPrev;
}
}
HeapAllocGlobal::deallocate(head);
}
HeapAlloc::~HeapAlloc() {
if (mNumAllocations) {
DEBUG_BREAK("Destruction of not freed Allocator");
#ifdef MEM_STACK_TRACE
// TODO : log leaks and free them up
#endif
}
}
#endif

View file

@ -0,0 +1,149 @@
#include "HeapAllocatorGlobal.hpp"
#include "PrivateConfig.hpp"
#include "Debugging.hpp"
#include <stddef.h>
#include <cstdlib>
using namespace tp;
#if not defined(MEM_DEBUG)
// ----------------------- Release Implementation ---------------------------- //
void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
void HeapAllocGlobal::deallocate(void* aPtr) { free(aPtr); }
HeapAllocGlobal::~HeapAllocGlobal() = default;
#else
tp::MemHead* tp::HeapAllocGlobal::mEntry = nullptr;
tp::ualni tp::HeapAllocGlobal::mNumAllocations = 0;
// ----------------------- Debug Implementation ---------------------------- //
// |----------------|
// | MemHead |
// |----------------|
// | wrap top |
// |----------------| - Allocated Block Layout
// | data |
// |----------------|
// | wrap bottom |
// |----------------|
namespace tp {
struct MemHead {
MemHead* mPrev;
MemHead* mNext;
ualni mBlockSize;
#ifdef MEM_STACK_TRACE
CallStackSnapshots::StackShapshot mCallStack;
#endif
};
};
enum : ualni {
ALIGNED_SIZE = ENV_ALNI_SIZE_B,
WRAP_SIZE = MEM_WRAP_SIZE * ALIGNED_SIZE,
WRAP_VAL = MEM_WRAP_FILL_VAL,
HEAD_SIZE = sizeof(MemHead),
CLEAR_ALLOC_VAL = MEM_CLEAR_ON_ALLOC_VAL,
CLEAR_DEALLOC_VAL = MEM_CLEAR_ON_DEALLOC_VAL,
};
void* HeapAllocGlobal::allocate(ualni aBlockSize) {
static_assert(HEAD_SIZE % ALIGNED_SIZE == 0, "Heap Allocator Configuration Error");
if (aBlockSize % ALIGNED_SIZE) {
aBlockSize = (aBlockSize / ALIGNED_SIZE + 1) * ALIGNED_SIZE;
}
// 1) Allocate the block
auto head = (MemHead*)malloc(aBlockSize + WRAP_SIZE * 2 + HEAD_SIZE);
auto wrap_top = (int1*)(head + 1);
auto data = wrap_top + WRAP_SIZE;
auto wrap_bottom = data + aBlockSize;
if (!head) { return nullptr; }
head->mBlockSize = aBlockSize;
// 2) Link with existing blocks
mNumAllocations++;
if (mEntry) {
head->mNext = mEntry->mNext;
head->mPrev = mEntry->mPrev;
if (mEntry->mNext) mEntry->mNext->mPrev = head;
if (mEntry->mPrev) mEntry->mPrev->mNext = head;
}
else {
head->mNext = nullptr;
head->mPrev = nullptr;
}
mEntry = head;
// 3) Wrap fill
memsetv(wrap_top, WRAP_SIZE, WRAP_VAL);
memsetv(wrap_bottom, WRAP_SIZE, WRAP_VAL);
// 4) Trace the stack
#ifdef MEM_STACK_TRACE
head->mCallStack = gCallStackSnapshots.capture();
#endif
// 5) clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, aBlockSize, CLEAR_ALLOC_VAL);
#endif
return data;
}
void HeapAllocGlobal::deallocate(void* aPtr) {
// 1) Restore the pointers
auto head = ((MemHead*)(aPtr)) - 1;
auto wrap_top = (int1*)(head + 1);
auto data = wrap_top + WRAP_SIZE;
auto wrap_bottom = data + head->mBlockSize;
// 2) Unlink with blocks
mNumAllocations--;
if (mEntry->mNext) mEntry->mNext->mPrev = mEntry->mPrev;
if (mEntry->mPrev) mEntry->mPrev->mNext = mEntry->mNext;
if (head == mEntry) {
if (mEntry->mNext) {
mEntry = mEntry->mNext;
}
else {
mEntry = mEntry->mPrev;
}
}
// 3) Check the wrap
RelAssert(memequalv(wrap_top, WRAP_SIZE, WRAP_VAL) && memequalv(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!");
// 4) clear data
#ifdef MEM_CLEAR_ON_ALLOC
memsetv(data, head->mBlockSize, CLEAR_DEALLOC_VAL);
#endif
// 5) free the block
free(aPtr);
}
HeapAllocGlobal::~HeapAllocGlobal() {
// 1) Check for not deallocated memory
if (mNumAllocations) {
DEBUG_BREAK("Destruction of not freed Allocator");
#ifdef MEM_STACK_TRACE
// TODO: log leaks
#endif
}
}
#endif

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,131 @@
/*
*
Implementation:
* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk
* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on de-allocation in log time
*
* Allocations:
* 1) allocate with chunk stored in list entry
* 2) ...
*
* De-allocations:
* 1) binary-search with delete pointer to find desired chunk
* 2) ...
*
*/
#include "Allocators.hpp"
#include "PrivateConfig.hpp"
void tp::PoolAlloc::Chunks::add(Chunk* aChunk) {
// ensure order
auto smaller_address = findUtil(mBuff, mBuff + mUsedLen, aChunk);
for (auto iter = mBuff + mUsedLen; iter != smaller_address; iter--) { *(iter + 1) = *iter; }
*(smaller_address + 1) = aChunk;
mUsedLen++;
// check for buff overflow
if (mUsedLen == mLen) {
auto prevBuff = mBuff;
mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen * 2);
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
mLen *= 2;
HeapAllocGlobal::deallocate(prevBuff);
}
}
void tp::PoolAlloc::Chunks::remove(Chunk* aChunk) {
// ensure order
auto del_address = findUtil(mBuff, mBuff + mUsedLen, aChunk);
for (auto iter = del_address; iter != mBuff + mUsedLen; iter++) { *iter = *(iter + 1); }
mUsedLen--;
// check for buff low usage
if ((halnf)mUsedLen / (halnf)mLen < 0.25f) {
auto prevBuff = mBuff;
mBuff = (Chunk**)HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2);
memcp(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen);
mLen /= 2;
HeapAllocGlobal::deallocate(prevBuff);
}
}
tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::find(void* aPtr) {
return *findUtil(mBuff, mBuff + mUsedLen, aPtr);
}
tp::PoolAlloc::Chunk* tp::PoolAlloc::Chunks::findNotFull() {
for (ualni idx = 0; idx < mUsedLen; idx++) {
if (!mBuff[idx]->isFull()) {
return mBuff[idx];
}
}
return nullptr;
}
tp::PoolAlloc::Chunk** tp::PoolAlloc::Chunks::findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) {
auto range = ualni(aRight - aLeft);
if (range == 1) { return aLeft; }
auto middle = aLeft + range / 2;
return (aPtr > *middle) ? findUtil(middle, aRight, aPtr) : findUtil(aLeft, middle, aPtr);
}
tp::PoolAlloc::PoolAlloc(ualni aBlockSize, ualni aChunkSize) : mBlockSize(aBlockSize), mChunkSize(aChunkSize) {}
void* tp::PoolAlloc::allocate() {
if (!mFreeChunk || mFreeChunk->isFull()) {
auto new_free_chunk = mChunks.findNotFull();
if (!new_free_chunk) {
new_free_chunk = new ((Chunk*)HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(mBlockSize, mChunkSize);
ASSERT(new_free_chunk);
mChunks.add(new_free_chunk);
if (mFreeChunk) {
new_free_chunk->mNext = mFreeChunk->mNext;
new_free_chunk->mPrev = mFreeChunk->mPrev;
if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = new_free_chunk;
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = new_free_chunk;
}
else {
new_free_chunk->mNext = nullptr;
new_free_chunk->mPrev = nullptr;
}
mFreeChunk = new_free_chunk;
}
}
return mFreeChunk->allocate();
}
void tp::PoolAlloc::deallocate(void* aPtr) {
auto chunk = mChunks.find(aPtr);
chunk->deallocate(aPtr);
if (mFreeChunk->isEmpty()) {
Chunk* new_chunk = nullptr;
for (ualni idx = 0; idx < mChunks.mUsedLen; idx++) {
if (!mChunks.mBuff[idx]->isFull() && new_chunk != mFreeChunk) {
new_chunk = mChunks.mBuff[idx];
}
}
if (new_chunk) {
if (mFreeChunk->mNext) mFreeChunk->mNext->mPrev = mFreeChunk->mPrev;
if (mFreeChunk->mPrev) mFreeChunk->mPrev->mNext = mFreeChunk->mNext;
mChunks.remove(mFreeChunk);
HeapAllocGlobal::deallocate(mFreeChunk);
mFreeChunk = new_chunk;
}
}
}
tp::PoolAlloc::~PoolAlloc() = default;

View file

@ -0,0 +1,10 @@
#pragma once
#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units
#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value
#define MEM_CLEAR_ON_ALLOC // Clear data on allocation
#define MEM_CLEAR_ON_DEALLOC // Clear data on free
#define MEM_CLEAR_ON_DEALLOC_VAL 0xAA // Clear data on free
#define MEM_CLEAR_ON_ALLOC_VAL 0xCC // Clear data on free
#define MEM_STACK_TRACE // Save stack on allocation call
#define MEM_STACK_TRACE_MAX_DEPTH 32 // Call stack max depth

View file

@ -0,0 +1,29 @@
#pragma once
#include "BaseModule.hpp"
#include "HeapAllocatorGlobal.hpp"
#include "HeapAllocator.hpp"
#include "ChunkAllocator.hpp"
#include "PoolAllocator.hpp"
namespace tp {
extern ModuleManifest gModuleAllocator;
};
inline void* operator new(std::size_t aSize, void* aWhere) noexcept { return aWhere; }
void* operator new(std::size_t aSize);
void* operator new[](std::size_t aSize);
void operator delete(void* aPtr);
void operator delete[](void* aPtr);
void* operator new(std::size_t aSize, tp::HeapAlloc& aAlloc);
void* operator new[](std::size_t aSize, tp::HeapAlloc& aAlloc);
void operator delete(void* aPtr, tp::HeapAlloc& aAlloc);
void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc);
void* operator new(std::size_t aSize, tp::HeapAllocGlobal& aAlloc);
void* operator new[](std::size_t aSize, tp::HeapAllocGlobal& aAlloc);
void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc);
void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc);

View file

@ -0,0 +1,32 @@
#pragma once
#include "Environment.hpp"
namespace tp {
// Chunk Allocator
// Constant time allocations and de-allocations in any order.
// Memory blocks are fixed in size and number of blocks can not exceed given parameter.
struct ChunkAlloc {
ChunkAlloc(ualni aBlockSize, void* aMemory, ualni aMemSize);
ChunkAlloc(ualni aBlockSize, ualni aNBlocks);
void* allocate();
void deallocate(void* aPtr);
[[nodiscard]] bool isFull() const;
[[nodiscard]] bool isEmpty() const;
~ChunkAlloc();
private:
ualni mBSize; // Size of data in aligned units
ualni mNBlocks;
ualni* mBuff = nullptr;
ualni* mNextBlock = nullptr;
ualni mNFreeBlocks;
ualni mNInitBlocks = 0;
bool mOwnBuff = false;
};
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "Environment.hpp"
namespace tp {
struct HeapAlloc {
#ifdef MEM_DEBUG
ualni mNumAllocations = 0;
struct MemHeadLocal* mEntry = nullptr;
#endif
void* allocate(ualni aBlockSize);
void deallocate(void* aPtr);
~HeapAlloc();
};
};

View file

@ -0,0 +1,18 @@
#pragma once
#include "BaseModule.hpp"
namespace tp {
struct HeapAllocGlobal {
#ifdef MEM_DEBUG
static ualni mNumAllocations;
static struct MemHead* mEntry;
#endif
static void* allocate(ualni aBlockSize);
static void deallocate(void* aPtr);
~HeapAllocGlobal();
};
};

View file

@ -0,0 +1,13 @@
#pragma once
#include "HeapAllocatorGlobal.hpp"
namespace tp {
struct PickAlloc {
PickAlloc();
void* allocate(ualni aBlockSize);
void deallocate(void* aPtr);
~PickAlloc();
};
};

View file

@ -0,0 +1,42 @@
#pragma once
#include "ChunkAllocator.hpp"
namespace tp {
// Pool Allocator
// Overcomes chunk allocator fixed number of max allocations
struct PoolAlloc {
PoolAlloc(ualni aBlockSize, ualni aChunkSize);
void* allocate();
void deallocate(void* aPtr);
~PoolAlloc();
private:
struct Chunk : public ChunkAlloc {
Chunk(ualni aBlockSize, ualni aChunlSize) : ChunkAlloc(aBlockSize, aChunlSize) {}
Chunk* mNext = NULL;
Chunk* mPrev = NULL;
};
struct Chunks {
void add(Chunk*);
void remove(Chunk*);
Chunk* find(void* aPtr);
Chunk* findNotFull();
Chunk** mBuff = NULL;
ualni mUsedLen = 0;
ualni mLen = 0;
private:
Chunk** findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr);
};
Chunks mChunks;
Chunk* mFreeChunk = NULL;
ualni mBlockSize;
ualni mChunkSize;
};
};

305
Allocators/tests/Tests.cpp Normal file
View file

@ -0,0 +1,305 @@
#include "Allocators.hpp"
#include <math.h>
#include <stdio.h>
int main() {
tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocators, NULL };
tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies);
if (!TestModule.initialize()) {
return 1;
}
tp::HeapAllocGlobal alloc;
int* val = new(alloc) int();
delete(alloc, val);
}
struct test_struct {
tp::alni val = 0;
test_struct() { val = 1; }
~test_struct() { val = -1; }
bool operator==(const test_struct& in) { return in.val == val; }
};
template <tp::alni size>
struct allocator_test {
test_struct data[size];
bool is_allocated[size];
tp::alni n_loaded = 0;
test_struct* allocations[size];
tp::AbstractAllocator* alloc;
tp::AbstractAllocator* parent_alloc;
const char* allocator_name = NULL;
tp::alni rand_idx(bool state) {
RAND:
tp::alni idx = (tp::alni)(tp::randf() * (size + 1));
CLAMP(idx, 0, size - 1);
if (state == is_allocated[idx]) {
goto RAND;
}
return idx;
}
allocator_test(tp::AbstractAllocator* palloc, const char* pallocator_name,
tp::AbstractAllocator* p_parent_alloc) {
allocator_name = pallocator_name;
parent_alloc = p_parent_alloc;
alloc = palloc;
for (tp::alni i = 0; i < size; i++) {
RAND:
tp::alni val = tp::alni(tp::randf() * (size + 100.f));
for (tp::alni check_idx = 0; check_idx < size; check_idx++) {
if (data[check_idx].val == val) {
goto RAND;
}
}
data[i].val = val;
is_allocated[i] = false;
allocations[i] = 0;
}
}
void verify_integrity() {
// verify data integrity
for (tp::alni i = 0; i < size; i++) {
if (is_allocated[i]) {
assert(*allocations[i] == data[i] && "data is currupted\n");
}
}
if (alloc->isWrapSupport()) assert(!alloc->isWrapCorrupted());
if (parent_alloc && parent_alloc->isWrapSupport())
assert(!parent_alloc->isWrapCorrupted());
verify_sizes();
}
void verify_sizes() {
return;
#ifdef MEM_TRACE
assert(alloc->sizeInuse() == n_loaded * sizeof(test_struct) &&
"invalid inuse size\n");
assert(alloc->sizeReserved() >= n_loaded * (tp::alni)sizeof(test_struct) &&
"invalid reserved size\n");
#endif
}
void load_item(tp::alni idx) {
if (!is_allocated[idx]) {
allocations[idx] = new (alloc) test_struct();
assert(allocations[idx] && "allocator returned NULL");
allocations[idx]->val = data[idx].val;
is_allocated[idx] = true;
n_loaded++;
verify_integrity();
}
}
void unload_item(tp::alni idx) {
if (is_allocated[idx]) {
verify_integrity();
delete allocations[idx];
is_allocated[idx] = false;
n_loaded--;
verify_integrity();
}
}
void change_states(tp::Range<tp::alni> rg, bool state, bool reversed = false,
bool random = false) {
for (auto i : rg) {
tp::alni idx = i;
if (random) {
idx = rand_idx(state);
}
else if (reversed) {
idx = size - i - 1;
}
(state) ? load_item(idx) : unload_item(idx);
}
}
// full down-up load then up-down unload
void test1() {
change_states({ 0, size }, 1);
change_states({ 0, size }, 0, true);
}
// full down-up load then down-up unload
void test2() {
change_states({ 0, size }, 1);
change_states({ 0, size }, 0);
}
// full random load then random unload
void test3() {
change_states({ 0, size }, 1, 0, 1);
change_states({ 0, size }, 0, 0, 1);
}
// multipul tests 1-3
void test4() {
test1();
test1();
test2();
test2();
test3();
test3();
}
static tp::alnf sineupf(tp::alnf asize, tp::alnf x, bool reverse) {
tp::alnf end = 4 * PI;
tp::alnf a = (2 / 7.f) * asize;
tp::alnf b = end / asize;
tp::alni c = ((-1 * reverse) + (1 * !reverse));
tp::alnf c1 = (x - (end * reverse)) / b;
tp::alnf c2 = (a * sin(x - (end * reverse)));
tp::alnf out = c1 + c2;
return c * out;
}
// sin load & sin unload with ~1/2 drop factor
void test5() {
tp::alnf end = 4 * PI;
tp::alnf step = end / 4.f;
for (char i = 0; i < 2; i++) {
for (tp::alnf x = 0; x <= end; x += step) {
tp::alni target_alloc_count = (tp::alni)ceil(sineupf(size, x, i));
CLAMP(target_alloc_count, 0, size);
while (n_loaded > target_alloc_count) {
unload_item(rand_idx(0));
}
while (n_loaded < target_alloc_count) {
load_item(rand_idx(1));
}
}
}
}
#ifdef MEM_WRAP
void check_wrap(tp::alni offset, bool after) {
CLAMP(offset, 1, WRAP_LEN);
test_struct* ts = allocations[rand_idx(0)];
tp::alni shift = (sizeof(test_struct) * after) + (offset - 1) * after -
offset * (!after);
tp::uint1* address = (((tp::uint1*)ts) + shift);
tp::uint1 val = *address;
*address = 5;
assert(alloc->isWrapCorrupted());
*address = val;
}
#endif
// mem guards test
void test6() {
change_states({ 0, size }, 1);
#ifdef MEM_WRAP
for (tp::alni after = 0; after < 2; after++) {
for (tp::alni offset = 1; offset <= WRAP_LEN; offset++) {
check_wrap(offset, after);
}
}
#endif
change_states({ 0, size }, 0);
}
void run_tests() {
try {
test1();
test2();
test3();
test4();
test5();
if (alloc->isWrapSupport()) {
test6();
}
printf("%s - passed\n", allocator_name);
if (!alloc->isWrapSupport()) {
printf(" WARNING: %s has no wrap support!! \n", allocator_name);
}
}
catch (...) {
printf("%s - failed\n", allocator_name);
}
}
};
void heap_alloc_test() {
tp::alloc_init(); {
try {
allocator_test<150> hatest(tp::heapalloc, "heap allocator", NULL);
hatest.run_tests();
}
catch (...) {
printf("heap alloc failed\n");
}
} tp::alloc_uninit();
}
void chunk_alloc_test() {
tp::alloc_init(); {
try {
tp::ChunkAlloc calloc(sizeof(test_struct), 50);
allocator_test<50> ca_test(&calloc, "chunk allocator", tp::heapalloc);
ca_test.run_tests();
}
catch (...) {
printf("chunk alloc failed\n");
}
} tp::alloc_uninit();
}
void pool_alloc_test() {
tp::alloc_init(); {
try {
tp::PoolAlloc palloc(sizeof(test_struct), 50);
allocator_test<150> pa_test(&palloc, "pool allocator", tp::heapalloc);
pa_test.run_tests();
}
catch (...) {
printf("chunk alloc failed\n");
}
} tp::alloc_uninit();
}
void allocators_test() {
printf("running tests on alocators:\n");
heap_alloc_test();
chunk_alloc_test();
pool_alloc_test();
}
CROSSPLATFORM_MAIN;
int main(int argc, char* argv[]) {
tp::print_env_info();
allocators_test();
}

View file