From d4c558a59a253e68f0cab81cc0e88a2a80ad7a63 Mon Sep 17 00:00:00 2001 From: Ilusha <63184036+IlyaShurupov@users.noreply.github.com> Date: Fri, 26 May 2023 01:35:29 +0300 Subject: [PATCH] Initial --- .gitignore | 6 + README.md | 42 ++ private/Allocators.cpp | 25 ++ private/ChunkAllocator.cpp | 113 +++++ private/HeapAllocator.cpp | 72 +++ private/HeapAllocatorGlobal.cpp | 146 ++++++ private/PickAllocator.cpp | 1 + private/PoolAllocator.cpp | 132 ++++++ private/PrivateConfig.hpp | 12 + private/StackTrace.cpp | 186 ++++++++ private/StackTrace.hpp | 43 ++ public/Allocators.hpp | 26 ++ public/ChunkAllocator.hpp | 33 ++ public/HeapAllocator.hpp | 20 + public/HeapAllocatorGlobal.hpp | 19 + public/PickAllocator.hpp | 13 + public/PoolAllocator.hpp | 42 ++ tests/Tests.cpp | 18 + tests/alloctests.cpp | 291 ++++++++++++ tests/alloctests.h | 3 + tools/MemBenchmark/BenchmarkEntry.cpp | 0 tools/MemBenchmark/benchmark_simple.cpp | 172 ++++++++ tools/MemBenchmark/benchmarker.cpp | 563 ++++++++++++++++++++++++ tools/MemBenchmark/benchmarker.h | 100 +++++ tools/MemBenchmark/collector.cpp | 119 +++++ tools/MemBenchmark/collector.h | 43 ++ tools/MemBenchmark/patterns.cpp | 0 tools/MemBenchmark/patterns.h | 174 ++++++++ tools/MemLeaks/MemleaksEntry.cpp | 93 ++++ tools/MemLeaks/memleaks.cpp | 403 +++++++++++++++++ tools/MemLeaks/memleaks.h | 137 ++++++ tools/MemUsage/MemUsageEntry.cpp | 1 + tools/MemUsage/memusage.cpp | 401 +++++++++++++++++ tools/MemUsage/memusage.h | 401 +++++++++++++++++ 34 files changed, 3850 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 private/Allocators.cpp create mode 100644 private/ChunkAllocator.cpp create mode 100644 private/HeapAllocator.cpp create mode 100644 private/HeapAllocatorGlobal.cpp create mode 100644 private/PickAllocator.cpp create mode 100644 private/PoolAllocator.cpp create mode 100644 private/PrivateConfig.hpp create mode 100644 private/StackTrace.cpp create mode 100644 private/StackTrace.hpp create mode 100644 public/Allocators.hpp create mode 100644 public/ChunkAllocator.hpp create mode 100644 public/HeapAllocator.hpp create mode 100644 public/HeapAllocatorGlobal.hpp create mode 100644 public/PickAllocator.hpp create mode 100644 public/PoolAllocator.hpp create mode 100644 tests/Tests.cpp create mode 100644 tests/alloctests.cpp create mode 100644 tests/alloctests.h create mode 100644 tools/MemBenchmark/BenchmarkEntry.cpp create mode 100644 tools/MemBenchmark/benchmark_simple.cpp create mode 100644 tools/MemBenchmark/benchmarker.cpp create mode 100644 tools/MemBenchmark/benchmarker.h create mode 100644 tools/MemBenchmark/collector.cpp create mode 100644 tools/MemBenchmark/collector.h create mode 100644 tools/MemBenchmark/patterns.cpp create mode 100644 tools/MemBenchmark/patterns.h create mode 100644 tools/MemLeaks/MemleaksEntry.cpp create mode 100644 tools/MemLeaks/memleaks.cpp create mode 100644 tools/MemLeaks/memleaks.h create mode 100644 tools/MemUsage/MemUsageEntry.cpp create mode 100644 tools/MemUsage/memusage.cpp create mode 100644 tools/MemUsage/memusage.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f85b25e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea +tmp +bin +build +lib +install diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e4f3e7 --- /dev/null +++ b/README.md @@ -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 file 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 diff --git a/private/Allocators.cpp b/private/Allocators.cpp new file mode 100644 index 0000000..86785fb --- /dev/null +++ b/private/Allocators.cpp @@ -0,0 +1,25 @@ + +#include "allocators.hpp" + +#include "filesystem.h" + +static tp::ModuleManifest* sModuleDependencies[] = { &tp::gModuleFilesystem, NULL }; +tp::ModuleManifest tp::gModuleAllocator = ModuleManifest("Allocators", NULL, NULL, sModuleDependencies); + + +//void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; } + +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); } \ No newline at end of file diff --git a/private/ChunkAllocator.cpp b/private/ChunkAllocator.cpp new file mode 100644 index 0000000..a66a997 --- /dev/null +++ b/private/ChunkAllocator.cpp @@ -0,0 +1,113 @@ + +/* +* +* 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() { + RelAssert(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() { return !mNFreeBlocks; } +bool ChunkAlloc::isEmpty() { return mNFreeBlocks == mNBlocks; } + +ChunkAlloc::~ChunkAlloc() { + if (mOwnBuff) { + HeapAllocGlobal::deallocate(mBuff); + } +} \ No newline at end of file diff --git a/private/HeapAllocator.cpp b/private/HeapAllocator.cpp new file mode 100644 index 0000000..b520957 --- /dev/null +++ b/private/HeapAllocator.cpp @@ -0,0 +1,72 @@ + +#include "heapallocator.hpp" +#include "PrivateConfig.hpp" + +#include +#include + +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*)mAlloc.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 = NULL; + head->mPrev = NULL; + } + 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->mNext; + } + } + + mAlloc.deallocate(head); +} + +HeapAlloc::~HeapAlloc() { + if (mNumAllocations) { + DBG_BREAK("Destruction of not freed Allocator"); + + #ifdef MEM_STACK_TRACE + #endif + } +} + +#endif + diff --git a/private/HeapAllocatorGlobal.cpp b/private/HeapAllocatorGlobal.cpp new file mode 100644 index 0000000..4bade50 --- /dev/null +++ b/private/HeapAllocatorGlobal.cpp @@ -0,0 +1,146 @@ + +#include "heapallocator.hpp" +#include "PrivateConfig.hpp" + +#include "StackTrace.hpp" + +#include +#include + +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() {}; + +#else + +tp::MemHead* tp::HeapAllocGlobal::mEntry = NULL; +tp::ualni tp::HeapAllocGlobal::mNumAllocations = NULL; + +// ----------------------- 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 NULL; } + 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 = NULL; + head->mPrev = NULL; + } + 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->mNext; + } + } + + // 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) { + DBG_BREAK("Destruction of not freed Allocator"); + + #ifdef MEM_STACK_TRACE + #endif + } +} + +#endif + diff --git a/private/PickAllocator.cpp b/private/PickAllocator.cpp new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/private/PickAllocator.cpp @@ -0,0 +1 @@ + diff --git a/private/PoolAllocator.cpp b/private/PoolAllocator.cpp new file mode 100644 index 0000000..6388811 --- /dev/null +++ b/private/PoolAllocator.cpp @@ -0,0 +1,132 @@ + +/* +* +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 deallocation in log time +* +* Allocations: +* 1) allocate with chunk stored in list entry +* 2) ... +* +* Deallocations: +* 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 / 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 NULL; +} + +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); + RelAssert(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 = NULL; + new_free_chunk->mPrev = NULL; + } + + 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 = NULL; + 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() { +} diff --git a/private/PrivateConfig.hpp b/private/PrivateConfig.hpp new file mode 100644 index 0000000..4f09d53 --- /dev/null +++ b/private/PrivateConfig.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "PublicConfig.hpp" + +#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 \ No newline at end of file diff --git a/private/StackTrace.cpp b/private/StackTrace.cpp new file mode 100644 index 0000000..a3f7170 --- /dev/null +++ b/private/StackTrace.cpp @@ -0,0 +1,186 @@ + +#include "StackTrace.hpp" +#include "PrivateConfig.hpp" + +#include +#include +#include +#include + +#pragma comment(lib, "dbghelp.lib") + +using namespace tp; + +tp::CallStackSnapshots tp::gCallStackSnapshots; + +// ----------------------- Dict ----------------------- // + +CallStackSnapshots::SnapshotsDict::SnapshotsDict() { + mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize); + memsetv(mTable, sizeof(StackShapshot) * mSize, 0); +} + +CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::getSlot(ualni aIdx) { + return mTable[aIdx]; +} + +alni CallStackSnapshots::SnapshotsDict::presents(StackShapshot aPtr) { + return findSlotRead(aPtr); +} + +void CallStackSnapshots::SnapshotsDict::put(StackShapshot aPtr) { + auto idx = findSlotWrite(aPtr); + mTable[idx] = aPtr; + mEntries++; + + if ((halnf)mEntries / mSize > 2.f / 3.f) { + resize(); + } +} + +void CallStackSnapshots::SnapshotsDict::resize() { + alni nslots_old = mSize; + auto table_old = mTable; + + mSize *= 2; + mTable = (StackShapshot*)malloc(sizeof(StackShapshot) * mSize); + memsetv(mTable, sizeof(StackShapshot) * mSize, 0); + mEntries = 0; + + for (alni i = 0; i < nslots_old; i++) { + if (!table_old[i]) { + continue; + } + + alni idx = findSlotWrite(table_old[i]); + mTable[idx] = table_old[i]; + mEntries++; + } + + delete[] table_old; +} + +alni CallStackSnapshots::SnapshotsDict::findSlotRead(StackShapshot key) { + ualni const hased_key = hash(key); + ualni const mask = mSize - 1; + ualni const shift = (hased_key >> 5) & ~1; + alni idx = hased_key & mask; +NEXT: + if (!mTable[idx]) { + return -1; + } + if (compare(mTable[idx], key)) { + return idx; + } +SKIP: + idx = ((5 * idx) + 1 + shift) & mask; + goto NEXT; +} + +ualni CallStackSnapshots::SnapshotsDict::findSlotWrite(StackShapshot key) { + ualni const hased_key = hash(key); + ualni const mask = mSize - 1; + ualni const shift = (hased_key >> 5) & ~1; + ualni idx = hased_key & mask; +NEXT: + if (!mTable[idx]) { + return idx; + } + idx = ((5 * idx) + 1 + shift) & mask; + goto NEXT; +} + +CallStackSnapshots::StackShapshot CallStackSnapshots::SnapshotsDict::newStackSnapshot(ualni aDepth) { + auto const size = sizeof(FramePointer) * (aDepth + 1); + auto out = (StackShapshot*)malloc(size); + memsetv(out, size, 0); +} + +ualni CallStackSnapshots::SnapshotsDict::hash(StackShapshot snapshot) { + ualni out = 0; + for (FramePointer* iter = snapshot; iter; iter++) { out += *iter; } + return out; +} + +bool CallStackSnapshots::SnapshotsDict::compare(StackShapshot left, StackShapshot right) { + FramePointer* iter_left = left; + FramePointer* iter_right = right; + do { + if (*iter_left != *iter_right) { + return false; + } + iter_left++; + iter_right++; + } while (iter_left && iter_right); + if (*iter_left != *iter_right) { + return false; + } + return true; +} + +CallStackSnapshots::SnapshotsDict::~SnapshotsDict() { + for (ualni idx = 0; idx < mSize; idx++) { + if (mTable[idx]) { + free(mTable[idx]); + } + } + free(mTable); +} + +// ----------------------- CallStackSnapshots ----------------------- // + +CallStackSnapshots::StackShapshot CallStackSnapshots::getStack(ualni& len) { + enum { MAX_DEPTH = MEM_STACK_TRACE_MAX_DEPTH }; + static FramePointer pointers[MAX_DEPTH]; + len = 0; + + CONTEXT context; + RtlCaptureContext(&context); + + STACKFRAME64 stackFrame; + ZeroMemory(&stackFrame, sizeof(STACKFRAME64)); + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Mode = AddrModeFlat; + stackFrame.AddrPC.Offset = context.Rip; + stackFrame.AddrFrame.Offset = context.Rbp; + stackFrame.AddrStack.Offset = context.Rsp; + + HANDLE processHandle = GetCurrentProcess(); + HANDLE threadHandle = GetCurrentThread(); + + while ( + len < MAX_DEPTH && + StackWalk64(IMAGE_FILE_MACHINE_AMD64, processHandle, threadHandle, &stackFrame, &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL) + ) + { + pointers[len] = stackFrame.AddrFrame.Offset; + len++; + } + + return pointers; +} + +CallStackSnapshots::StackShapshot CallStackSnapshots::capture() { + ualni len; + auto stack = getStack(len); + auto idx = mSnapshots.presents(stack); + + if (idx == -1) { + auto new_snapshot = mSnapshots.newStackSnapshot(len + 1); + memcp(new_snapshot, stack, len * sizeof(FramePointer)); + new_snapshot[len] = 0; + mSnapshots.put(new_snapshot); + return new_snapshot; + } + + return mSnapshots.getSlot(idx); +} + +void CallStackSnapshots::saveToFile(StackShapshot* snapshots, const char* filepath) { + +} + +CallStackSnapshots::StackShapshot* CallStackSnapshots::loadFromFile(const char* filepath) { + +} \ No newline at end of file diff --git a/private/StackTrace.hpp b/private/StackTrace.hpp new file mode 100644 index 0000000..62c03a9 --- /dev/null +++ b/private/StackTrace.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "common.h" + +namespace tp { + + struct CallStackSnapshots { + + typedef alni FramePointer; + typedef FramePointer* StackShapshot; // NULL Terminated Frames + + StackShapshot capture(); + + void saveToFile(StackShapshot* snapshots, const char* filepath); + StackShapshot* loadFromFile(const char* filepath); + + private: + + struct SnapshotsDict { + SnapshotsDict(); + StackShapshot newStackSnapshot(ualni aDepth); + void put(StackShapshot aPtr); + StackShapshot getSlot(ualni aIdx); + alni presents(StackShapshot aPtr); + ~SnapshotsDict(); + + private: + StackShapshot* mTable = NULL; + uhalni mSize = 512; + uhalni mEntries = 0; + + ualni hash(StackShapshot snapshot); + bool compare(StackShapshot left, StackShapshot right); + alni findSlotRead(StackShapshot key); + ualni findSlotWrite(StackShapshot key); + void resize(); + } mSnapshots; + + StackShapshot getStack(ualni& len); + }; + + extern CallStackSnapshots gCallStackSnapshots; +}; diff --git a/public/Allocators.hpp b/public/Allocators.hpp new file mode 100644 index 0000000..506ae01 --- /dev/null +++ b/public/Allocators.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "HeapAllocator.hpp" +#include "ChunkAllocator.hpp" +#include "PoolAllocator.hpp" + +namespace tp { + extern ModuleManifest gModuleAllocator; +}; + +inline void* operator new(size_t aSize, void* aWhere) noexcept { return aWhere; } + +void* operator new(size_t aSize); +void* operator new[](size_t _Size); +void operator delete(void* aPtr); +void operator delete[](void* aPtr); + +void* operator new(size_t aSize, tp::HeapAlloc& aAlloc); +void* operator new[](size_t _Size, tp::HeapAlloc& aAlloc); +void operator delete(void* aPtr, tp::HeapAlloc& aAlloc); +void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc); + +void* operator new(size_t aSize, tp::HeapAllocGlobal& aAlloc); +void* operator new[](size_t _Size, tp::HeapAllocGlobal& aAlloc); +void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc); +void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc); \ No newline at end of file diff --git a/public/ChunkAllocator.hpp b/public/ChunkAllocator.hpp new file mode 100644 index 0000000..dc9a9cb --- /dev/null +++ b/public/ChunkAllocator.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "common.h" +#include "PublicConfig.hpp" + +namespace tp { + + // Chunk Allocator + // Constant time allocations and deallocations 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); + bool isFull(); + bool isEmpty(); + + ~ChunkAlloc(); + + private: + ualni mBSize; // Size of data in aligned units + ualni mNBlocks; + + ualni* mBuff; + ualni* mNextBlock = 0; + ualni mNFreeBlocks; + ualni mNInitBlocks = 0; + bool mOwnBuff = false; + }; +}; \ No newline at end of file diff --git a/public/HeapAllocator.hpp b/public/HeapAllocator.hpp new file mode 100644 index 0000000..b6f84aa --- /dev/null +++ b/public/HeapAllocator.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "HeapAllocatorGlobal.hpp" + +namespace tp { + + struct HeapAlloc { + +#ifdef MEM_DEBUG + HeapAllocGlobal mAlloc; + + ualni mNumAllocations = 0; + struct MemHeadLocal* mEntry = NULL; +#endif + + void* allocate(ualni aBlockSize); + void deallocate(void* aPtr); + ~HeapAlloc(); + }; +}; diff --git a/public/HeapAllocatorGlobal.hpp b/public/HeapAllocatorGlobal.hpp new file mode 100644 index 0000000..63f3875 --- /dev/null +++ b/public/HeapAllocatorGlobal.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "common.h" +#include "PublicConfig.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(); + }; +}; diff --git a/public/PickAllocator.hpp b/public/PickAllocator.hpp new file mode 100644 index 0000000..52b23bc --- /dev/null +++ b/public/PickAllocator.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "HeapAllocatorGlobal.hpp" + +namespace tp { + + struct PickAlloc { + PickAlloc(); + void* allocate(ualni aBlockSize); + void deallocate(void* aPtr); + ~PickAlloc(); + }; +}; diff --git a/public/PoolAllocator.hpp b/public/PoolAllocator.hpp new file mode 100644 index 0000000..fa68650 --- /dev/null +++ b/public/PoolAllocator.hpp @@ -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; + }; +}; diff --git a/tests/Tests.cpp b/tests/Tests.cpp new file mode 100644 index 0000000..18339e3 --- /dev/null +++ b/tests/Tests.cpp @@ -0,0 +1,18 @@ + +#include "allocators.hpp" + +int main() { + + tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleAllocator, NULL }; + tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); + if (!TestModule.initialize()) { + return 1; + } + + tp::HeapAllocGlobal alloc; + + int* val = new(alloc) int(); + delete(alloc, val); + + TestModule.deinitialize(); +} \ No newline at end of file diff --git a/tests/alloctests.cpp b/tests/alloctests.cpp new file mode 100644 index 0000000..44e935b --- /dev/null +++ b/tests/alloctests.cpp @@ -0,0 +1,291 @@ + +#include +#include + +#include "allocators.h" + +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 +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 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(); +} \ No newline at end of file diff --git a/tests/alloctests.h b/tests/alloctests.h new file mode 100644 index 0000000..9a1ba89 --- /dev/null +++ b/tests/alloctests.h @@ -0,0 +1,3 @@ + +#pragma once +void allocators_test(); \ No newline at end of file diff --git a/tools/MemBenchmark/BenchmarkEntry.cpp b/tools/MemBenchmark/BenchmarkEntry.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tools/MemBenchmark/benchmark_simple.cpp b/tools/MemBenchmark/benchmark_simple.cpp new file mode 100644 index 0000000..ed433d1 --- /dev/null +++ b/tools/MemBenchmark/benchmark_simple.cpp @@ -0,0 +1,172 @@ + + +#include "allocators.h" + +#include +#include "timer.h" + +struct AllocSpeedTets { + const char* test_desc = NULL; + virtual void exec() {}; +}; + +struct Test1 : AllocSpeedTets { + Test1() { + test_desc = "allocate lots of memory of same sizes, then free it all"; + } + + const int len = 1000; + int size = 100; + void* buff[1000]; + + void exec_util(tp::AbstractAllocator* alloc) { + for (size_t i = 0; i < len; i++) { + buff[i] = alloc->Alloc(size); + } + for (size_t i = 0; i < len; i++) { + alloc->Free(buff[i]); + } + } + + void exec() { + tp::time_ms start, end; + + tp::HeapAlloc malloc; + tp::PoolAlloc pool(0, 0); + tp::ChunkAlloc chunck(0, 0); + tp::PickAlloc pick; + + start = tp::get_time(); + + end = tp::get_time(); + printf("Malloc : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pool : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Chunk : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pick : %lli ms \n", end - start); + } +}; + +struct Test2 : AllocSpeedTets { + Test2() { + test_desc = "allocate lots of memory of different sizes, then free it all"; + } + + void exec() { + tp::time_ms start, end; + + tp::HeapAlloc malloc; + tp::PoolAlloc pool(0, 0); + tp::ChunkAlloc chunck(0, 0); + tp::PickAlloc pick; + + start = tp::get_time(); + end = tp::get_time(); + printf("Malloc : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pool : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Chunk : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pick : %lli ms \n", end - start); + } +}; + +struct Test3 : AllocSpeedTets { + Test3() { + test_desc = "allocate only a few blocks of memory, free them, and repeat this loop several times \ + \n (repeat for same - sized blocks and different - sized blocks)\n"; + } + + void exec() { + tp::time_ms start, end; + + tp::HeapAlloc malloc; + tp::PoolAlloc pool(0, 0); + tp::ChunkAlloc chunck(0, 0); + tp::PickAlloc pick; + + start = tp::get_time(); + end = tp::get_time(); + printf("Malloc : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pool : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Chunk : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pick : %lli ms \n", end - start); + } +}; + +struct Test4 : AllocSpeedTets { + Test4() { + test_desc = "allocate lots of memory of different sizes, free half of it(e.g.the even allocations), then allocateand free memory in a loop\n"; + } + + void exec() { + tp::time_ms start, end; + + tp::HeapAlloc malloc; + tp::PoolAlloc pool(0, 0); + tp::ChunkAlloc chunck(0, 0); + tp::PickAlloc pick; + + start = tp::get_time(); + end = tp::get_time(); + printf("Malloc : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pool : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Chunk : %lli ms \n", end - start); + + start = tp::get_time(); + end = tp::get_time(); + printf("Pick : %lli ms \n", end - start); + } +}; + +void banckmark() { + + tp::alloc_init(); + + const int tests_len = 1; + AllocSpeedTets* tests[] = { + new Test1(), + new Test2(), + new Test3(), + new Test4(), + }; + + for (int i = 0; i < tests_len; i++) { + printf("Test %i: \n %s\n", i, tests[i]->test_desc); + tests[i]->exec(); + + delete tests[i]; + } + + tp::alloc_uninit(); +} \ No newline at end of file diff --git a/tools/MemBenchmark/benchmarker.cpp b/tools/MemBenchmark/benchmarker.cpp new file mode 100644 index 0000000..f4deadc --- /dev/null +++ b/tools/MemBenchmark/benchmarker.cpp @@ -0,0 +1,563 @@ + +#include "benchmarker.h" + +#include "implot.h" +#include "ImGuiUtils.h" + +tp::alni hash(const tp::string& val) { + return tp::hash(val.cstr()); +} + +config glb_cfg = { + + .live_update = false, + + .heap = true, + .pool = true, + .chunk = true, + + .current_sizing_pattern = 0, + .current_loading_pattern = 0, + .current_ordering_pattern = 0, + + .update = 0, + + .time_per_instruction = true, + .mem_per_instruction = true, + .avreging = 1, + + .chunk_bsize = 100, + .chunk_blen = 100, + + .pool_bsize = 100, + .pool_blen = 100, +}; + +benchmarker::benchmarker() /*: ImGui::CompleteApp()*/ { + + i_count = 0; + pattern_out = NULL; + out.reserve(3); + + patterns.put("constant", new const_pattern()); + patterns.put("linear", new linear_pattern()); + patterns.put("random", new random_pattern()); +} + +void benchmarker::output_draw() { + if (ImGui::Begin("Overview")) { + + if (is_output) { + if (ImGui::TreeNode("total time")) { + for (auto& i : tp::Range(0, 3)) { + if (out[i]) { + if (out[i]->total_time < 1000) { + ImGui::Text("%s : %f 10e-9", out[i]->alloc_type, out[i]->total_time); + } else if (out[i]->total_time < 1000000) { + ImGui::Text("%s : %f 10e-6", out[i]->alloc_type, out[i]->total_time / 1000.f); + } else if (out[i]->total_time < 1000000000) { + ImGui::Text("%s : %f 10e-3", out[i]->alloc_type, out[i]->total_time / 1000000.f); + } else { + ImGui::Text("%s : %f ", out[i]->alloc_type, out[i]->total_time / 1000000000.f); + } + } + } + ImGui::TreePop(); + } + + if (ImGui::TreeNode("failures")) { + if ((out[0] && out[0]->failed) || (out[1] && out[1]->failed) || (out[2] && out[2]->failed)) { + for (auto& i : tp::Range(0, 3)) { + if (out[i] && out[i]->failed) + ImGui::Text("%s failed", out[i]->alloc_type); + } + } else { + ImGui::Text("all succeeded"); + } + ImGui::TreePop(); + } + } + } + ImGui::End(); + + + if (cfg.time_per_instruction) { + if (ImGui::Begin("Graphs")) { + if (is_output) { + if (ImPlot::BeginPlot("Time(ns) vs Alloc / Free inst")) { + if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->time.buff(), (int) i_count); + if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->time.buff(), (int) i_count); + if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->time.buff(), (int) i_count); + ImPlot::EndPlot(); + } + } + } + ImGui::End(); + } + + if (cfg.mem_per_instruction) { + if (ImGui::Begin("Graphs")) { + if (is_output) { + if (ImPlot::BeginPlot("Memory (bytes) at Instruction state")) { + if (cfg.heap) ImPlot::PlotLine("halloc", x_axis, out[0]->mem.buff(), (int) i_count); + if (cfg.pool) ImPlot::PlotLine("pool", x_axis, out[1]->mem.buff(), (int) i_count); + if (cfg.chunk) ImPlot::PlotLine("chunk", x_axis, out[2]->mem.buff(), (int) i_count); + ImPlot::EndPlot(); + } + } + } + ImGui::End(); + } + + if (ImGui::Begin("Pattern")) { + if (is_output) { + if (ImPlot::BeginPlot("Alloc/Free instruction info vs instruction idx")) { + if (cfg.current_sizing_pattern) ImPlot::PlotLine("allocated item size", x_axis, pattern_out->alloc_size.buff(), (int) i_count); + if (cfg.current_ordering_pattern) ImPlot::PlotLine("used data item idx", x_axis, pattern_out->data_idx.buff(), (int) i_count); + if (cfg.current_loading_pattern) ImPlot::PlotLine("total items allocated", x_axis, pattern_out->items_loaded.buff(), (int) i_count); + ImPlot::EndPlot(); + } + } + } + ImGui::End(); +} + +benchmarker::~benchmarker() { + for (auto iter : patterns) { + delete iter->val; + } + clear_out(); +} + +void benchmarker::select_pattern() { + if (patterns.size()) { + const char** pattern_names = new const char* [patterns.size()]; + + const char* current_opattern_name = NULL; + const char* current_lpattern_name = NULL; + const char* current_spattern_name = NULL; + + for (auto pattern_name : patterns) { + pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr(); + if (pattern_name->val == glb_cfg.current_ordering_pattern) + current_opattern_name = pattern_name->key.cstr(); + if (pattern_name->val == glb_cfg.current_sizing_pattern) + current_spattern_name = pattern_name->key.cstr(); + if (pattern_name->val == glb_cfg.current_loading_pattern) + current_lpattern_name = pattern_name->key.cstr(); + } + + if (ImGui::BeginCombo("Ordering", current_opattern_name)) { + for (int n = 0; n < patterns.size(); n++) { + bool is_selected = (current_opattern_name == pattern_names[n]); + if (ImGui::Selectable(pattern_names[n], is_selected)) { + current_opattern_name = pattern_names[n]; + glb_cfg.current_ordering_pattern = patterns.get(pattern_names[n]); + } + } + ImGui::EndCombo(); + } + + if (ImGui::BeginCombo("Loading", current_lpattern_name)) { + for (int n = 0; n < patterns.size(); n++) { + bool is_selected = (current_lpattern_name == pattern_names[n]); + if (ImGui::Selectable(pattern_names[n], is_selected)) { + current_lpattern_name = pattern_names[n]; + glb_cfg.current_loading_pattern = patterns.get(pattern_names[n]); + } + } + ImGui::EndCombo(); + } + + if (ImGui::BeginCombo("Sizing", current_spattern_name)) { + for (int n = 0; n < patterns.size(); n++) { + bool is_selected = (current_spattern_name == pattern_names[n]); + if (ImGui::Selectable(pattern_names[n], is_selected)) { + current_spattern_name = pattern_names[n]; + glb_cfg.current_sizing_pattern = patterns.get(pattern_names[n]); + } + } + ImGui::EndCombo(); + } + delete pattern_names; + } +} + +void benchmarker::pattern_combo(const char*& current) { + if (patterns.size()) { + const char** pattern_names = new const char* [patterns.size()]; + + for (auto pattern_name : patterns) { + pattern_names[pattern_name.entry_idx] = pattern_name->key.cstr(); + } + + if (ImGui::BeginCombo("Patterns", current)) { + for (int n = 0; n < patterns.size(); n++) { + bool is_selected = (current == pattern_names[n]); + if (ImGui::Selectable(pattern_names[n], is_selected)) { + current = pattern_names[n]; + } + } + ImGui::EndCombo(); + } + delete pattern_names; + } +} + +void benchmarker::pattern_generator() { + + + static tp::alni selected_idx = -1; + static pattern* child_pattern_active = NULL; + + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.1f); + + pattern* pattern_edit = NULL; + if (ImGui::Begin("Pattern Generator")) { + + if (ImGui::SubMenuBegin("Selector", 1)) { + + const char* prev_pattern = pattern_generator_active; + pattern_combo(pattern_generator_active); + + child_pattern_active = (prev_pattern == pattern_generator_active) ? child_pattern_active : NULL; + + if (pattern_generator_active) { + tp::alni idx = patterns.presents(pattern_generator_active); + if (!MAP_VALID_IDX(idx)) { + pattern_edit = 0; + pattern_generator_active = 0; + } else { + pattern_edit = patterns[idx]; + } + } + + //ImGui::Separator(); + + static char create_name[100] = {"new pattern name"}; + ImGui::InputText("pattern name", create_name, 100); + + if (ImGui::Button("Create")) { + if (!patterns.presents(create_name)) { + pattern* new_pt = new pattern(); + new_pt->build_in = false; + new_pt->pattern_name = create_name; + tp::string id = create_name; + id.capture(); + patterns.put(id, new_pt); + } else { + ImGui::Notify("Such Pattern Already Exists", 3); + } + } + + if (pattern_edit) { + ImGui::SameLine(); + if (ImGui::Button("Delete")) { + if (pattern_edit->build_in) { + ImGui::Notify("Cant Remove Built-in Patterns", 3); + } else { + patterns.remove(pattern_generator_active); + delete pattern_edit; + pattern_edit = NULL; + pattern_generator_active = NULL; + } + } + + ImGui::SameLine(); + if (ImGui::Button("Rename")) { + if (pattern_edit->build_in) { + ImGui::Notify("Cant Rename Built-in Patterns", 3); + } else { + patterns.remove(pattern_generator_active); + patterns.put(create_name, pattern_edit); + } + } + } + ImGui::SubMenuEnd(1); + } + + if (pattern_generator_active) { + tp::alni idx = patterns.presents(pattern_generator_active); + if (!MAP_VALID_IDX(idx)) { + pattern_edit = 0; + pattern_generator_active = 0; + } else { + pattern_edit = patterns[idx]; + } + } + + if (!pattern_edit) { + ImGui::Text("Select pattern to edit or create one"); + ImGui::End(); + return; + } + + if (ImGui::SubMenuBegin("Preview", 1)) { + static float preview_res_scale = 0.05f; + int resolution = (int) (1000 * preview_res_scale); + CLAMP(resolution, 3, 10000); + float* x_axis = new float[resolution]; + float* y_axis = new float[resolution]; + + float x = 0; + float step = 1.f / (resolution - 1); + for (tp::alni idx = 0; idx < resolution; idx++) { + x_axis[idx] = x; + y_axis[idx] = (tp::flt4) pattern_edit->get_y(&patterns, x); + x += step; + } + + + if (ImPlot::BeginPlot("Pattern")) { + ImPlot::PlotLine("toggle", x_axis, y_axis, resolution); + ImPlot::EndPlot(); + } + + delete x_axis; + delete y_axis; + ImGui::SliderFloat("graph resolution", &preview_res_scale, 0.f, 1.f); + ImGui::SubMenuEnd(1); + } + + if (ImGui::SubMenuBegin("Compositor", 1)) { + + if (!pattern_edit->build_in) { + + if (ImGui::SubMenuBegin("Child Patterns", 2)) { + ImGui::BeginListBox(""); + for (tp::alni idx = 0; idx < pattern_edit->regions.length(); idx++) { + ImGui::PushID((int) idx); + if (ImGui::Button(pattern_edit->regions[idx].name.cstr())) { + child_pattern_active = patterns.get(pattern_edit->regions[idx].name); + selected_idx = idx; + } + if (selected_idx == idx) { + ImGui::SameLine(); ImGui::Text(" - Active"); + } + ImGui::PopID(); + } + ImGui::EndListBox(); + + if (selected_idx >= pattern_edit->regions.length()) { + selected_idx = -1; + } + + static const char* append_pattern = NULL; + bool add = ImGui::Button(" + "); + ImGui::SameLine(); + pattern_combo(append_pattern); + if (add) { + if (append_pattern) { + pattern_edit->regions.pushBack(child_pattern(append_pattern)); + } else { + ImGui::Notify("Select a Pattern to Append"); + } + } + + if (child_pattern_active && selected_idx != -1) { + + if (ImGui::Button(" Up ")) { + if (selected_idx > 0) { + tp::string tmp = pattern_edit->regions[selected_idx].name; + pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx - 1]; + pattern_edit->regions[selected_idx - 1] = tmp; + selected_idx--; + } + } + ImGui::SameLine(); + if (ImGui::Button("Down")) { + if (selected_idx < pattern_edit->regions.length() - 1) { + tp::string tmp = pattern_edit->regions[selected_idx].name; + pattern_edit->regions[selected_idx] = pattern_edit->regions[selected_idx + 1]; + pattern_edit->regions[selected_idx + 1] = tmp; + selected_idx++; + } + } + ImGui::SameLine(); + if (ImGui::Button("Remove")) { + pattern_edit->regions.remove(selected_idx); + selected_idx = pattern_edit->regions.length() - 1; + } + } else { + ImGui::Text("Select Child Pattern"); + } + ImGui::SubMenuEnd(2); + } + + if (child_pattern_active && selected_idx != -1) { + if (ImGui::SubMenuBegin("child Pattern Properties", 2)) { + ImGui::SliderFloat("Point", &pattern_edit->regions[selected_idx].point, 0.f, 1.f); + ImGui::SliderFloat("Lower lim", &pattern_edit->regions[selected_idx].lowerlim, 0.f, 1.f); + ImGui::SliderFloat("Upper lim", &pattern_edit->regions[selected_idx].uppernlim, 0.f, 1.f); + if (child_pattern_active->build_in) { + } + ImGui::SubMenuEnd(2); + } + } + } else { + ImGui::Text("Can't Edit Built-In Patterns"); + } + ImGui::SubMenuEnd(1); + } + + ImGui::Separator(); + + } + ImGui::End(); +} + +void benchmarker::MainDrawTick() { + + analize(glb_cfg); + + + ImGui::BeginGroup(); + + if (ImGui::WindowEditor("Properties")) { + + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f); + + { + if (ImGui::Button("Run")) { + glb_cfg.update = true; + } + + bool prev_val = glb_cfg.live_update; + ImGui::SameLine(); ImGui::Checkbox("Live Update", &glb_cfg.live_update); + if (prev_val != glb_cfg.live_update) { + glb_cfg.update = true; + } + } + + if (ImGui::SubMenuBegin("General", 1)) { + ImGui::InputInt("Averaging", &glb_cfg.avreging, 1, 100); ImGui::ToolTip("Number of tests to be averaged"); + ImGui::Checkbox("Collect time per instruction", &glb_cfg.time_per_instruction); + ImGui::Checkbox("Collect mem per instruction", &glb_cfg.mem_per_instruction); + + ImGui::SubMenuEnd(1); + } + + if (ImGui::SubMenuBegin("Testing Pattern", 1)) { + + select_pattern(); + + ImGui::InputInt("size", &glb_cfg.pt_scale.size, 1, 100); ImGui::ToolTip("Y Scale of sizing pattern"); + ImGui::InputInt("items", &glb_cfg.pt_scale.items, 1, 100); ImGui::ToolTip("Y Scale of ordering and loading patterns"); + ImGui::InputInt("iterations", &glb_cfg.pt_scale.iterations, 1, 100); ImGui::ToolTip("X Scale of all patterns"); + + ImGui::SubMenuEnd(1); + } + + if (ImGui::SubMenuBegin("Allocators", 1)) { + ImGui::Checkbox("heap", &glb_cfg.heap); ImGui::SameLine(); ImGui::Checkbox("pool", &glb_cfg.pool); ImGui::SameLine(); ImGui::Checkbox("chunk", &glb_cfg.chunk); + + if (glb_cfg.chunk && ImGui::SubMenuBegin("Chunk", 2)) { + ImGui::InputInt("size", &glb_cfg.chunk_bsize, 1, 100); ImGui::ToolTip("Size of a slot in the chunk buffer"); + ImGui::InputInt("length", &glb_cfg.chunk_blen, 1, 100); ImGui::ToolTip("Number of slots in the chunk buffer"); + ImGui::SubMenuEnd(2); + } + if (glb_cfg.pool && ImGui::SubMenuBegin("Pool", 2)) { + ImGui::ToolTip("Generalized use of chunk allocator"); + ImGui::InputInt("size", &glb_cfg.pool_bsize, 1, 100); + ImGui::InputInt("length", &glb_cfg.pool_blen, 1, 100); + ImGui::SubMenuEnd(2); + } + ImGui::SubMenuEnd(1); + } + + //ImGui::PopItemWidth(); + } + ImGui::End(); + + output_draw(); + + pattern_generator(); + + ImGui::EndGroup(); +} + + +void benchmarker::analize(config pcfg) { + + if (!((pcfg.update) || (!(this->cfg == pcfg) && cfg.live_update))) { + return; + } + + this->cfg = pcfg; + + clear_out(); + + if (!pattern_analizer.init(&patterns, cfg.current_loading_pattern, cfg.current_ordering_pattern, cfg.current_sizing_pattern, &cfg.pt_scale)) { + if (pcfg.update) ImGui::Notify("invalid pattern configuration", 3); + glb_cfg.update = false; + cfg.update = false; + is_output = false; + return; + } + + reserve_out(&pattern_analizer); + + for (tp::alni iter = 0; iter < pcfg.avreging; iter++) { + init_allocators(pcfg); + if (cfg.heap) collect(&pattern_analizer, halloc, out[0]); + if (cfg.pool) collect(&pattern_analizer, palloc, out[1]); + if (cfg.chunk) collect(&pattern_analizer, calloc, out[2]); + dest_allocators(); + } + + for (auto& i : tp::Range(0, 3)) { + if (out[i]) out[i]->scale_all(1.f / pcfg.avreging); + } + + i_count = pattern_analizer.max_iterations(); + if (x_axis) { + delete x_axis; + x_axis = NULL; + } + x_axis = new tp::alnf[i_count]; + for (tp::alni iter = 0; iter < i_count; iter++) { + x_axis[iter] = (tp::alnf) iter; + } + + glb_cfg.update = false; + cfg.update = false; + is_output = true; +} + +void benchmarker::init_allocators(config& pcfg) { + if (cfg.heap) halloc = new tp::HeapAlloc(); + if (cfg.pool) palloc = new tp::PoolAlloc(pcfg.pool_bsize, pcfg.pool_blen); + if (cfg.chunk) calloc = new tp::ChunkAlloc(pcfg.chunk_bsize, pcfg.chunk_blen); +} + +void benchmarker::dest_allocators() { + try { + if (cfg.heap) delete halloc; + if (cfg.pool) delete palloc; + if (cfg.chunk) delete calloc; + } catch (...) { + } + + halloc = NULL; + palloc = NULL; + calloc = NULL; +} + +void benchmarker::clear_out() { + for (auto i : tp::Range(0, 3)) { + if (out[i]) { + delete out[i]; + out[i] = NULL; + } + } + + if (pattern_out) delete pattern_out; + pattern_out = NULL; +} + +void benchmarker::reserve_out(test_pattern* pattern) { + pattern_out = new pattern_histogram(pattern); + if (cfg.heap) out[0] = new allocator_histogram(pattern, "heap", cfg.time_per_instruction, cfg.mem_per_instruction); + if (cfg.pool) out[1] = new allocator_histogram(pattern, "pool", cfg.time_per_instruction, cfg.mem_per_instruction); + if (cfg.chunk) out[2] = new allocator_histogram(pattern, "chunk", cfg.time_per_instruction, cfg.mem_per_instruction); +} \ No newline at end of file diff --git a/tools/MemBenchmark/benchmarker.h b/tools/MemBenchmark/benchmarker.h new file mode 100644 index 0000000..96c42f0 --- /dev/null +++ b/tools/MemBenchmark/benchmarker.h @@ -0,0 +1,100 @@ + +#pragma once + +#include "patterns.h" + +#include "allocators.h" +#include "array.h" + +#include "gl.h" +#include "glcommon.h" +#include "window.h" + +enum class load_type { + LINEAR, + SINE, + STEPS, + RANDOM +}; + +enum class order_type { + LINEAR, + LINEAR_REVERSED, + RANDOM, +}; + +struct config { + // general + bool live_update = false; + + bool heap = true; + bool pool = true; + bool chunk = true; + + // pattern + pattern* current_sizing_pattern = 0; + pattern* current_loading_pattern = 0; + pattern* current_ordering_pattern = 0; + pattern_scale pt_scale; + + tp::alni update = 0; + bool time_per_instruction = 0; + bool mem_per_instruction = 0; + int avreging; + + // allocators + int chunk_bsize = 0; + int chunk_blen = 0; + + int pool_bsize = 0; + int pool_blen = 0; + + bool operator==(const config& in) { + return tp::memequal(this, (config*)(&in), sizeof(config)); + } +}; + +struct benchmarker { + + config cfg; + bool is_output = false; + + // allocators + tp::HeapAlloc* halloc = NULL; + tp::PoolAlloc* palloc = NULL; + tp::ChunkAlloc* calloc = NULL; + + + tp::Array out; + pattern_histogram* pattern_out; + + tp::alni i_count; + tp::alnf* x_axis = NULL; + + tp::HashMap patterns; + + pattern_reader pattern_analizer; + + const char* pattern_generator_active = NULL; + + benchmarker(); + ~benchmarker(); + + test_pattern* get_pattern(config& cfg); + + void pattern_combo(const char*&); + void analize(config cfg); + void select_pattern(); + void output_draw(); + + void MainDrawTick(); + + void pattern_generator(); + + void init_allocators(config& cfg); + + void dest_allocators(); + + void clear_out(); + void reserve_out(test_pattern* pattern); +}; \ No newline at end of file diff --git a/tools/MemBenchmark/collector.cpp b/tools/MemBenchmark/collector.cpp new file mode 100644 index 0000000..0afc0e0 --- /dev/null +++ b/tools/MemBenchmark/collector.cpp @@ -0,0 +1,119 @@ + +#include "collector.h" + +#include + +allocator_histogram::allocator_histogram(test_pattern* pt, const char* alloc_type, bool p_time_per_inst, bool p_mem_per_inst) { + this->alloc_type = alloc_type; + time_per_inst = p_time_per_inst; + mem_per_inst = p_mem_per_inst; + + if (time_per_inst) { + time.reserve(pt->max_iterations()); + } + if (mem_per_inst) { + mem.reserve(pt->max_iterations()); + } + data.reserve(pt->data_count()); + + total_time = 0; + failed = false; +} + +void allocator_histogram::scale_all(tp::alnf fac) { + for (auto& iter : time) { + iter.data() = fac * iter.data(); + } + for (auto& iter : mem) { + iter.data() = fac * iter.data(); + } + total_time = fac * total_time; +} + +allocator_histogram::~allocator_histogram() { +} + +void allocator_histogram::mark_resourses_usage(tp::alni idx, tp::alnf p_time, tp::alni p_mem, bool add) { + if (mem.length() > idx) + add ? mem[idx] += (tp::alnf)p_mem : mem[idx] = (tp::alnf)p_mem; + if (time.length() > idx) + add ? time[idx] += p_time : time[idx] = p_time; +} + +bool execute_instruction(tp::AbstractAllocator* alloc, bool load, tp::alni size, tp::uint1*& data) { + bool failed = false; + try { + if (load) { + if (!data) { + data = (tp::uint1*)alloc->Alloc(size); + if (!data) { + failed = true; + } + } + } + else { + if (data) { + alloc->Free(data); + data = NULL; + } + } + } + catch (...) { + failed = true; + } + return !failed; +} + + + +void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram) { + + tp::alni iter_idx = 0; + tp::alni nimtems_loaded = 0; + + auto total_st = std::chrono::high_resolution_clock::now(); + + for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) { + + tp::alni target_nimtems_loaded = pattern->pick_alloc_count(iter_idx); + tp::alni data_idx = pattern->pick_idx(iter_idx); + tp::alni load_size = pattern->pick_size(iter_idx); + + auto iter_st = std::chrono::high_resolution_clock::now(); + while (nimtems_loaded != target_nimtems_loaded) { + bool load = nimtems_loaded < target_nimtems_loaded; + if (!execute_instruction(alloc, load, load_size, histogram->data[data_idx])) { + histogram->mark_resourses_usage(iter_idx, 0, 0, 0); + histogram->failed = true; + break; + } + nimtems_loaded += (tp::alni)load + (-1 * (tp::alni)(!load)); + } + auto iter_nd = std::chrono::high_resolution_clock::now(); + tp::alni dur = std::chrono::duration_cast(iter_nd - iter_st).count(); + histogram->mark_resourses_usage(iter_idx, tp::alnf(dur), alloc->sizeReserved(), 1); + } + + // clear all out + for (iter_idx = 0; iter_idx < pattern->max_iterations(); iter_idx++) { + if (histogram->data[iter_idx]) { + execute_instruction(alloc, false, 0, histogram->data[iter_idx]); + } + } + + auto total_nd = std::chrono::high_resolution_clock::now(); + tp::alni dur = std::chrono::duration_cast(total_nd - total_st).count(); + histogram->total_time += dur; +} + +pattern_histogram::pattern_histogram(test_pattern* pt) { + alloc_size.reserve(pt->max_iterations()); + data_idx.reserve(pt->max_iterations()); + items_loaded.reserve(pt->max_iterations()); + + for (auto& i : tp::Range(0, pt->max_iterations())) { + alloc_size[i] = (tp::alnf) pt->pick_size(i); + data_idx[i] = (tp::alnf)pt->pick_idx(i); + items_loaded[i] = (tp::alnf)pt->pick_alloc_count(i); + } +} diff --git a/tools/MemBenchmark/collector.h b/tools/MemBenchmark/collector.h new file mode 100644 index 0000000..abc0745 --- /dev/null +++ b/tools/MemBenchmark/collector.h @@ -0,0 +1,43 @@ + +#pragma once + +#include "array.h" + +class test_pattern { +public: + virtual tp::alni pick_size(tp::alni iter) { return 0; }; + virtual tp::alni max_size() { return 0; }; + virtual tp::alni pick_alloc_count(tp::alni iter) { return 0; }; + virtual tp::alni max_iterations() { return 0; }; + virtual tp::alni pick_idx(tp::alni iter) { return 0; }; + virtual tp::alni data_count() { return 0; }; +}; + +struct pattern_histogram { + tp::Array alloc_size; + tp::Array data_idx; + tp::Array items_loaded; + + pattern_histogram(test_pattern* pt); +}; + +struct allocator_histogram { + const char* alloc_type; + + tp::alnf total_time; + tp::Array time; + tp::Array mem; + bool failed; + + tp::Array data; + bool time_per_inst; + bool mem_per_inst; + + allocator_histogram(test_pattern* pt, const char* alloc_type, bool time_per_inst, bool mem_per_inst); + ~allocator_histogram(); + + void mark_resourses_usage(tp::alni idx, tp::alnf time, tp::alni mem, bool add); + void scale_all(tp::alnf fac); +}; + +void collect(test_pattern* pattern, tp::AbstractAllocator* alloc, allocator_histogram* histogram); \ No newline at end of file diff --git a/tools/MemBenchmark/patterns.cpp b/tools/MemBenchmark/patterns.cpp new file mode 100644 index 0000000..e69de29 diff --git a/tools/MemBenchmark/patterns.h b/tools/MemBenchmark/patterns.h new file mode 100644 index 0000000..7fe33ec --- /dev/null +++ b/tools/MemBenchmark/patterns.h @@ -0,0 +1,174 @@ + +#pragma once + +#include "strings.h" + +#include + +#include "collector.h" + +tp::alni hash(const tp::string& val); + +#include "map.h" + +enum class leav_pattern_type { + LINEAR, + RANDOM, + SINE, + CONST, +}; + +struct child_pattern { + child_pattern() {} + + child_pattern(tp::string _name) { name = _name; } + + float uppernlim = 1.f; + float lowerlim = 0.f; + float point = 1.f; + tp::string name; +}; + +struct pattern { + + leav_pattern_type type = leav_pattern_type::CONST; + tp::string pattern_name; + + tp::Array regions; + bool build_in = true; + + pattern() { + } + + tp::alnf get_y(tp::HashMap* patterns, tp::alnf x) { + assert(x <= 1.0001f && x >= -0.00001f); + + if (!regions.length()) { + return pure_get_y(x); + } + + float offset = 0.f; + for (tp::alni i = 0; i < regions.length(); i++) { + tp::alni idx = patterns->presents(regions[i].name); + if (!MAP_VALID_IDX(idx)) { + return 0.f; + } + pattern* child = (*patterns)[idx]; + assert(child); + + float range = regions[i].point * (1.f - offset); + if (offset + range > x) { + return regions[i].lowerlim + + (child->get_y(patterns, (x - offset) / range) * + (regions[i].uppernlim - regions[i].lowerlim)); + } + offset += range; + } + return 0; + } + + virtual tp::alnf pure_get_y(tp::alnf x) { return 0; } + + ~pattern() {} +}; + +// -------------------- build-in patterns ---------------------------- // + +struct const_pattern : pattern { + float val = 1.f; + const_pattern() { + type = leav_pattern_type::CONST; + pattern_name = "const"; + build_in = true; + } + + tp::alnf pure_get_y(tp::alnf x) override { return val; } +}; + +struct linear_pattern : pattern { + bool reversed = false; + linear_pattern() { + type = leav_pattern_type::LINEAR; + pattern_name = "linear"; + build_in = true; + } + + tp::alnf pure_get_y(tp::alnf x) override { return reversed ? 1.f - x : x; } +}; + +struct random_pattern : pattern { + random_pattern() { + type = leav_pattern_type::RANDOM; + build_in = true; + } + + tp::alnf pure_get_y(tp::alnf x) override { return tp::randf(); } +}; + +// -------------------- build-in patterns end ---------------------------- // + +struct pattern_scale { + int items = 0; + int size = 0; + int iterations = 0; +}; + +class pattern_reader : public test_pattern { + public: + bool init(tp::HashMap* p_patterns, pattern* p_lpattern, + pattern* p_opattern, pattern* p_spattern, pattern_scale* p_scale) { + lpattern = p_lpattern; + opattern = p_opattern; + spattern = p_spattern; + + scale = p_scale; + + patterns = p_patterns; + return verify_rulles(); + } + + bool verify_rulles() { + if (!lpattern || !opattern || !spattern) { + return false; + } + + bool out = true; + out &= scale->items > 0; + out &= scale->size > 0; + out &= scale->iterations > 0; + return out; + } + + tp::HashMap* patterns; + + pattern* lpattern = NULL; + pattern* opattern = NULL; + pattern* spattern = NULL; + + pattern_scale* scale = 0; + + tp::alnf get_x_val(tp::alni iter) { + tp::alnf out = iter / (tp::alnf)scale->iterations; + return out > 1 ? 1.f : out; + } + + tp::alni pick_size(tp::alni iter) override { + return (tp::alni)(scale->size * spattern->get_y(patterns, get_x_val(iter))); + } + + tp::alni pick_alloc_count(tp::alni iter) override { + return (tp::alni)(scale->items * lpattern->get_y(patterns, get_x_val(iter))); + } + + tp::alni pick_idx(tp::alni iter) override { + tp::alni out = (tp::alni) (scale->items * opattern->get_y(patterns, get_x_val(iter))); + CLAMP(out, 0, scale->items - 1); + return out; + } + + tp::alni max_size() override { return scale->size; } + + tp::alni max_iterations() override { return scale->iterations; } + + tp::alni data_count() override { return scale->iterations; } +}; diff --git a/tools/MemLeaks/MemleaksEntry.cpp b/tools/MemLeaks/MemleaksEntry.cpp new file mode 100644 index 0000000..56d6d3c --- /dev/null +++ b/tools/MemLeaks/MemleaksEntry.cpp @@ -0,0 +1,93 @@ + +#include "SimpleGui.h" +#include "debugui.h" +#include "memleaks.h" +#include "timer.h" + +struct MemLeaksGUI { + + tp::glw::Window window; + tp::glw::DebugUI ui; + tp::glw::Canvas canvas; + tp::glw::WindowSimpleInputs inputs; + tp::glw::WindowsHeaderWidget header; + tp::MemLeaksData mLeaks; + tp::MemLeaksTreeView mView; + tp::halnf mHeaderHeight = 5.5f; + tp::Timer timer; + + MemLeaksGUI(const char* path) : window(), ui(window, debuguiCallBack, this), mLeaks(path), timer(10) { + canvas.setCol1({ 0.5f, 0.5f, 0.5f, 0.5f }); + window.mAppearence.mHiden = false; + mView.setTarget(&mLeaks); + } + + void proc() { + window.pollEvents(); + inputs.update(window, 1.f); + + header.header_rec = { (inputs.mWindowSizeMM.x - 60.f) / 2.f, 3.f, 60.f, mHeaderHeight }; + mView.rect = { 0.f, 0.f, inputs.mWindowSizeMM.x, inputs.mWindowSizeMM.y }; + + mView.proc(); + + if (inputs.MoveUp()) { + mView.zoom_factor -= 0.2f; + } + else if (inputs.MoveDown()) { + mView.zoom_factor += 0.2f; + } + mView.zoom_factor = tp::clamp(mView.zoom_factor, 0.f, 1.f); + + mView.vieport_crs = inputs.mCrs; + mView.vieport_crs_delta = inputs.mCrsmDelta * -1.f; + mView.mouse_down = inputs.Activated(); + mView.mouse_hold = inputs.Anticipating(); + mView.mouse_up = inputs.Selected(); + + header.proc(window, inputs); + } + + void draw() { + window.beginDraw(); { + canvas.beginDraw({ { 0, 0 }, inputs.mWindowSizeMM }, window.mDevice.mDPMM, 1.f); { + mView.draw(&canvas); + header.draw(canvas); + } canvas.endDraw(); + ui.drawDebugUI(window.mDevice.mDPMM); + } window.endDraw(); + } + + void run() { + while (!window.mEvents.mClose) { + proc(); + if (window.mEvents.mRedraw) { + draw(); + } + + timer.wait(); + timer.reset(); + } + } + + void debugui() {} + static void debuguiCallBack(void* self) { ((MemLeaksGUI*)self)->debugui(); } + ~MemLeaksGUI() {} +}; + +int main(char argc, char* argv[]) { + tp::set_working_dir(); + + tp::ModuleManifest* ModuleDependencies[] = { &tp::gModuleGlw, NULL }; + tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); + if (!TestModule.initialize()) { + return 1; + } + + { + MemLeaksGUI gui(argc > 1 ? argv[1] : "rsc/debug.memleaks"); + gui.run(); + } + + TestModule.deinitialize(); +} \ No newline at end of file diff --git a/tools/MemLeaks/memleaks.cpp b/tools/MemLeaks/memleaks.cpp new file mode 100644 index 0000000..529a47d --- /dev/null +++ b/tools/MemLeaks/memleaks.cpp @@ -0,0 +1,403 @@ + +#include "memleaks.h" + +using namespace tp; + +void MemLeaksData::Frame::dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom) { + if (frame.flag == Frame::Flags::INUSE) { return; } + frame.flag = Frame::Flags::INUSE; + exec(frame, custom); + for (auto call_info : frame.callers) { + dfs(*call_info->val.caller, exec, custom); + } + frame.flag = Frame::Flags::NONE; +} + +MemLeaksData::MemLeaksData(tp::string afilename) { + filename = afilename; + load_leaks(); + + rootframe.name = "root"; +} + +void MemLeaksData::increase_caller_count(Frame& frame, FrameId caller_id) { + auto idx = frame.callers.presents(caller_id); + if (idx) { + frame.callers.getSlotVal(idx).count++; + } + else { + Frame* caller_frame = &frames.get(caller_id); + frame.callers.put(caller_id, { caller_frame, 1 }); + } +} + +void MemLeaksData::calc_max_level(Frame& frame, tp::alni& max_level) { + frame.flag = Frame::Flags::INUSE; + + for (auto call_info : frame.callers) { + auto caller = call_info->val.caller; + if (caller->flag != Frame::Flags::INUSE) { + if (caller->depth_level < frame.depth_level + 1) { + caller->depth_level = frame.depth_level + 1; + max_level = MAX(caller->depth_level, max_level); + calc_max_level(*call_info->val.caller, max_level); + } + } + } + + frame.flag = Frame::Flags::NONE; +} + +void MemLeaksData::count_level_users(Frame& frame, tp::alni& max_level, tp::Array& levels) { + if (frame.flag2 == 1) { + return; + } + frame.flag2 = 1; + levels[frame.depth_level].count_users += 1; + for (auto call_info : frame.callers) { + count_level_users(*call_info->val.caller, max_level, levels); + } +} + +void MemLeaksData::apply_position(Frame& frame, tp::Array& levels) { + if (frame.flag2 == 1) { + return; + } + frame.flag2 = 1; + + + auto x = -(levels[frame.depth_level].count_users - 1) * sapacing.x / 2 + sapacing.x * levels[frame.depth_level].used_idx; + frame.tree_view_pos.assign(x, frame.depth_level * sapacing.y); + levels[frame.depth_level].used_idx++; + + for (auto call_info : frame.callers) { + apply_position(*call_info->val.caller, levels); + } +}; + +void MemLeaksData::construct_tree() { + using namespace tp; + + // construct tree + for (auto leak : leaks) { + increase_caller_count(rootframe, leak.data()[0]); + for (auto i : Range(leak->length() - 1)) { + increase_caller_count(frames.get(leak.data()[i]), leak.data()[i + 1]); + } + } + + tp::alni max_depth_level = 0; + calc_max_level(rootframe, max_depth_level); + + levels.reserve(max_depth_level + 1); + + count_level_users(rootframe, max_depth_level, levels); + Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; }); + + apply_position(rootframe, levels); + Frame::dfs(rootframe, [](Frame& frame, void*) { frame.flag2 = 0; }); +} + +void MemLeaksData::make_connections() { + tp::halni len = 0; + for (auto& frame : frames) { + len += frame.iter->val.callers.size(); + } + mConnections.reserve(len); + tp::halni idx = 0; + for (auto& frame : frames) { + for (auto& caller : frame.iter->val.callers) { + caller.iter->val.caller; + mConnections[idx] = { caller.iter->val.caller, &frame.iter->val, caller.iter->val.count }; + idx++; + } + } +} + +void MemLeaksData::load_leaks() { + using namespace tp; + + File log(filename.cstr(), osfile_openflags::LOAD); + + if (!log.opened) { + status = LoadStatus::INVALID_FILE_PATH; + return; + } + + log.Preload(); + + const char logo[] = "memleaks\0"; + const char logo_len = 10; + char logo_loaded[logo_len]; + + log.read_bytes(logo_loaded, logo_len); + + if (!tp::memequal(logo_loaded, logo, logo_len)) { + status = LoadStatus::INVALID_FILE_FORMAT; + return; + } + + try { + alni leaks_len; + log.read(&leaks_len); + + leaks.reserve(leaks_len); + + Frame frame; + + for (alni idx = 0; idx < leaks_len; idx++) { + + tp::alni frames_len; + log.read(&frames_len); + + leaks[idx].reserve(frames_len); + + for (alni frame_idx = 0; frame_idx < frames_len; frame_idx++) { + + FrameId id; + + + log.read(&id); + leaks[idx][frame_idx] = id; + + //if (frames.presents(id)) { + //continue; + //} + + frame.name.load(&log); + frame.file.load(&log); + log.read(&frame.line); + + frame.id = id; + + frames.put(id, frame); + } + } + + construct_tree(); + make_connections(); + + } + catch (...) { + status = LoadStatus::INTERNAL_ERROR; + return; + } + + status = LoadStatus::DONE; +} + + +// ------------------------- Tree View Drawer -------------------------------------- // + +void MemLeaksTreeView::setTarget(MemLeaksData* aLeaks) { + leaks = aLeaks; + selected_node = NULL; +} + +void MemLeaksTreeView::proc() { + if (!leaks) { + return; + } + + // handle selection + if (mouse_down) { + selected_node = NULL; + + void (*find_selected)(Frame & frame, void* vec) = [](Frame& frame, void* self_ptr) { + auto self = (MemLeaksTreeView*)self_ptr; + + auto node_rec = self->nodeBoundsScaled(frame); + //auto vieport = self->rect; + + if (node_rec.inside(self->vieport_crs)) { + self->selected_node = &frame; + } + }; + + Frame::dfs(leaks->rootframe, find_selected, this); + } + + // handle mouse drag + if (vieport_crs_delta != 0.f && rect.inside(vieport_crs) && mouse_hold) { + if (selected_node) { + selected_node->tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval; + } + else { + tree_view_pos -= vieport_crs_delta / (tp::halnf)scaleval; + } + } + + // calc scale fac + { + // zoom_factor : + // 0 - 3 nodes visiable + // 1 - all nodes visiable + + auto max_tree_size = MAX(tree_size.x, tree_size.y); + auto max_node_size = MAX(node_size.x, node_size.y); + auto min_view_size = MIN(rect.z, rect.w) - 100; + + auto max = min_view_size / max_tree_size; + auto min = min_view_size / max_node_size; + + scaleval = (max - min) * zoom_factor + min; + } + + // calc tree size + void (*execf)(Frame & frame, void* vec) = [](Frame& frame, void* vecp) { + auto tree_min_max_pos = (tp::rectf*)vecp; + // if smaller than min + if (tree_min_max_pos->v1.x > frame.tree_view_pos.x) { + tree_min_max_pos->v1.x = frame.tree_view_pos.x; + } + if (tree_min_max_pos->v1.y > frame.tree_view_pos.y) { + tree_min_max_pos->v1.y = frame.tree_view_pos.y; + } + // if bigger than max + if (tree_min_max_pos->v2.x < frame.tree_view_pos.x) { + tree_min_max_pos->v2.x = frame.tree_view_pos.x; + } + if (tree_min_max_pos->v2.y < frame.tree_view_pos.y) { + tree_min_max_pos->v2.y = frame.tree_view_pos.y; + } + }; + + tree_size = 0; + tp::rectf tree_min_max_pos = { FLT_MAX, FLT_MIN }; + Frame::dfs(leaks->rootframe, execf, &tree_min_max_pos); + tree_size = tree_min_max_pos.v2 - tree_min_max_pos.v1; + tree_size += node_size; +} + +void MemLeaksTreeView::draw(tp::glw::Canvas* drawer) { + + drawer->setCol1(col.bg); + drawer->rect(rect, 2.f); + + drawer->setCol1(col.text); + if (!leaks) { + drawer->text("Not Loaded", rect, 5); + return; + } + + switch (leaks->status) { + case MemLeaksData::LoadStatus::INVALID_FILE_PATH: + drawer->text("INVALID_FILE_PATH", rect, 5); + return; + case MemLeaksData::LoadStatus::INVALID_FILE_FORMAT: + drawer->text("INVALID_FILE_FORMAT", rect, 5); + return; + case MemLeaksData::LoadStatus::INTERNAL_ERROR: + drawer->text("INTERNAL_ERROR", rect, 5); + return; + } + + for (auto& frame : leaks->frames) { + drawNode(frame.iter->val, drawer); + } + + for (auto& connection : leaks->mConnections) { + drawConnection(*connection.data().caller, *connection.data().target, connection.data().count, drawer); + } + + if (selected_node) { + auto flag = tp::glw::Canvas::Align(tp::glw::Canvas::LEFT | tp::glw::Canvas::TOP); + tp::rectf info_rec = rect; + info_rec.pos.x += 5; + info_rec.pos.y += 25; + drawer->text(selected_node->name, info_rec, 4, flag); + info_rec.pos.y += 10; + drawer->text(selected_node->file, info_rec, 4, flag); + info_rec.pos.y += 10; + drawer->text(tp::halni(selected_node->line), info_rec, 4, flag); + } +} + +void MemLeaksTreeView::drawNode(Frame& node, tp::glw::Canvas* drawer) { + auto rec = nodeBoundsScaled(node); + + // outside + if (!rec.overlap(rect)) { + node.flag = Frame::Flags::NONE; + return; + } + + rec.clamp(rect); + + drawer->setCol1((&node == selected_node) ? col.node_active : col.node); + drawer->setCol2(col.node_outline); + drawer->rect(rec, node_rounding, outline_size); + + drawer->setCol1(col.text); + auto prev = drawer->mClamping; + rec.pos.x += 2; + rec.size.x -= 4; + drawer->setClamping(rec); + drawer->text(node.name, rec, text_size, tp::glw::Canvas::LEFT_MIDDLE); + drawer->setClamping(prev); +} + +void MemLeaksTreeView::drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer) { + + node_size *= 1.1; + auto rec1 = nodeBoundsScaled(from); + auto rec2 = nodeBoundsScaled(to); + node_size /= 1.1; + + auto c1 = scalePoint(from.tree_view_pos + tree_view_pos); + auto c2 = scalePoint(to.tree_view_pos + tree_view_pos); + + rec1.clamp_outside(c1, c2); + rec2.clamp_outside(c1, c2); + + auto viewport = rect; + + if (!viewport.clamp_inside(c1, c2)) { + return; + } + + auto dir = (c2 - c1).unitv(); + auto side = dir.normal() * arrow_size * scaleval; + + auto ab = c2 - dir * arrow_size * scaleval; + auto ae = c2; + auto al = ab + side; + auto ar = ab - side; + + if (&from == selected_node || &to == selected_node) { + //col = ImColor(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + } + + drawer->setCol1(col.arrow); + + drawer->trig({ ae.x, ae.y }, { al.x, al.y }, { ar.x, ar.y }); + drawer->line({ c1.x, c1.y }, { c2.x, c2.y }, line_thik); + + if (&from == selected_node || &to == selected_node) { + auto text_pos = c1 + (dir * (tp::halnf)(c1 - c2).length() * 0.8); + tp::string count = call_count; + drawer->setCol1(col.text); + drawer->text(count.cstr(), { text_pos.x, text_pos.y, 0, 0 }, text_size); + } +} + +tp::vec2f MemLeaksTreeView::scalePoint(const tp::vec2f in) { + return in * (tp::halnf)scaleval + rect.pos + rect.size / 2; +} + +tp::rectf MemLeaksTreeView::nodeBounds(Frame& node) { + tp::rectf out; + out.pos = node.tree_view_pos + tree_view_pos; + out.pos -= node_size / 2; + out.size = node_size; + return out; +} + +tp::rectf MemLeaksTreeView::nodeBoundsScaled(Frame& node) { + auto out = nodeBounds(node); + auto p1 = scalePoint(out.pos); + auto p3 = scalePoint(out.p3()); + out.pos = p1; + out.size = p3 - p1; + return out; +} \ No newline at end of file diff --git a/tools/MemLeaks/memleaks.h b/tools/MemLeaks/memleaks.h new file mode 100644 index 0000000..25c53c9 --- /dev/null +++ b/tools/MemLeaks/memleaks.h @@ -0,0 +1,137 @@ + +#include "map.h" +#include "array.h" +#include "stringt.h" +#include "rect.h" +#include "color.h" +#include "canvas.h" + +namespace tp { + + struct MemLeaksData { + + typedef tp::ualni FrameId; + typedef tp::Array MemLeak; + + struct Frame; + + struct CallerInfo { + Frame* caller = NULL; + tp::ualni count = 0; + }; + + struct Frame { + tp::string name; + tp::string file; + tp::ualni line = 0; + tp::ualni id = 0; + + tp::HashMap callers; + + tp::vec2f tree_view_pos = 0; + bool collapced = true; + + enum class Flags { + NONE, + INUSE, + } flag = Flags::NONE; + + tp::alni flag2 = 0; + tp::alni depth_level = 0; + + static void dfs(Frame& frame, void (*exec)(Frame& frame, void* custom), void* custom = 0); + }; + + Frame rootframe; + tp::HashMap frames; + tp::Array leaks; + + struct Connection { + Frame* caller = NULL; + Frame* target = NULL; + tp::ualni count = 0; + }; + + tp::Array mConnections; + + tp::string filename; + tp::vec2f sapacing = { 60.f, 15.f }; + + enum class LoadStatus { + DONE, + INVALID_FILE_PATH, + INVALID_FILE_FORMAT, + INTERNAL_ERROR, + } status; + + MemLeaksData(tp::string afilename); + + private: + + struct LevelInfo { + tp::alni count_users; + tp::alni used_idx; + }; + + tp::Array levels; + + void increase_caller_count(Frame& frame, FrameId caller_id); + void calc_max_level(Frame& frame, tp::alni& max_level); + void count_level_users(Frame& frame, tp::alni& max_level, tp::Array& levels); + void apply_position(Frame& frame, tp::Array& levels); + void construct_tree(); + void load_leaks(); + void make_connections(); + }; + + struct MemLeaksTreeView { + + // inputs + tp::halnf zoom_factor = 1.f; + tp::rectf rect = 0.f; + tp::vec2f vieport_crs = 0.f; + tp::vec2f vieport_crs_delta = 0.f; + bool mouse_down = false; + bool mouse_hold = false; + bool mouse_up = false; + + // appearence + struct Colors { + tp::rgba node = { 0.1f, 0.1f, 0.1f, 1.f }; + tp::rgba node_outline = { 0.5f, 0.5f, 0.5f, 1.f }; + tp::rgba node_active = { 0.2f, 0.2f, 0.2f, 1.f }; + tp::rgba bg = { 0.15f, 0.15f, 0.15f, 1.f }; + tp::rgba text = { 0.9f, 0.9f, 0.9f, 1.f }; + tp::rgba arrow = { 0.5f, 0.5f, 0.5f, 1.f }; + } col; + + tp::halnf text_size = 3; + tp::halnf arrow_size = 2; + tp::halnf outline_size = 0.5f; + tp::halnf line_thik = 0.2f; + tp::halnf node_rounding = 1; + + void setTarget(MemLeaksData* aLeaks); + + void proc(); + void draw(tp::glw::Canvas* drawer); + + private: + + typedef MemLeaksData::Frame Frame; + + MemLeaksData* leaks = NULL; + Frame* selected_node = NULL; + bool inside_node = false; + tp::alnf scaleval = 1.0; + tp::vec2f tree_size = 0.f; + tp::vec2f tree_view_pos = { 0.f, -50.f }; + tp::vec2f node_size = { 50, 10 }; + + void drawNode(Frame& node, tp::glw::Canvas* drawer); + void drawConnection(Frame& from, Frame& to, tp::halni call_count, tp::glw::Canvas* drawer); + tp::vec2f scalePoint(const tp::vec2f in); + tp::rectf nodeBounds(Frame& node); + tp::rectf nodeBoundsScaled(Frame& node); + }; +}; \ No newline at end of file diff --git a/tools/MemUsage/MemUsageEntry.cpp b/tools/MemUsage/MemUsageEntry.cpp new file mode 100644 index 0000000..d3f5a12 --- /dev/null +++ b/tools/MemUsage/MemUsageEntry.cpp @@ -0,0 +1 @@ + diff --git a/tools/MemUsage/memusage.cpp b/tools/MemUsage/memusage.cpp new file mode 100644 index 0000000..4f7a4ba --- /dev/null +++ b/tools/MemUsage/memusage.cpp @@ -0,0 +1,401 @@ + + +#include "GuiWindow.h" + +#include "imgui.h" +#include "implot.h" +#include "nanovg.h" + +#include "strings.h" +#include "filesystem.h" +#include "array.h" +#include "list.h" +#include "map.h" + +#include "intersections.h" + +#include "pickalloc_analizer.h" +#include "pickalloc_cfg.h" + +#include + +class Memusage : public tp::GuiWindow { + + tp::string filename; + + static const char logo_len = 16; + const char logo[logo_len] = "memusage\0\0\0\0\0\0\0"; + char logo_loaded[logo_len]; + + PickAllocConfig cfg; + + enum class LoadStatus { + NONE, + DONE, + INVALID_FILE_PATH, + INVALID_FILE_FORMAT, + INTERNAL_ERROR, + } status; + + typedef tp::PickAllocDataAnalizer::Event Event; + typedef tp::alni SizeKey; + + struct Timeline { + tp::Array size; + tp::Array time; + tp::ualni peak = 0; + tp::ualni avereging = 0; + + void recordEvents(tp::Array& events) { + tp::ualni total_mem_usage = 0; + + size.extend(events.length()); + time.extend(events.length()); + + for (auto ev : events) { + + if (ev->dealloc) { + total_mem_usage -= ev->size; + } else { + total_mem_usage += ev->size; + } + + time[ev.idx()] = (tp::alnf) ev->time; + size[ev.idx()] = (tp::alnf) total_mem_usage; + + if (peak < total_mem_usage) { + peak = total_mem_usage; + } + } + + avereging = 1; + } + + void to_seconds() { + for (auto tm : time) { + tm.data() /= 1000; + } + } + }; + + // Contains events of fixed size + struct Group { + tp::ualni group_size = 0; + tp::Array events; + + tp::ualni flag = 0; + + Timeline timeline; + + Group(tp::ualni grp_size) : group_size(grp_size) {} + }; + + tp::HashMap groups; + tp::Array groups_sorted; + + public: + + Memusage(const char* filename) : GuiWindow() { + + this->mHeader.mAppTitle = "Mamusage Analizer"; + this->mHeader.mPadding = 10; + tp::string file_path = __FILE__; + tp::make_dir_str(file_path.get_writable()); + tp::make_dir_str(file_path.get_writable()); + this->mRscDirectory = file_path + "/"; + this->mGuiRootIsWindow = false; + LoadFonts(); + + this->filename = filename; + } + + ~Memusage() { + for (auto tl : groups) { + delete tl->val; + } + + if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes; + if (cfg.mChunkLengths) delete[] cfg.mChunkLengths; + } + + void run() { + + // display status + for (auto i : tp::Range(2)) { + procInputs(); + beginDraw(); + if (mNativeWindow.mEvents.mRedraw) { + DrawCallback(); + } + endDraw(); + mNativeWindow.mEvents.mRedraw = true; + } + + while (!mNativeWindow.mEvents.mTerminate) { + tp::Timer timer(5); + procInputs(); + beginDraw(); + if (mNativeWindow.mEvents.mRedraw) { + DrawCallback(); + } + endDraw(); + timer.wait(); + + if (status == LoadStatus::NONE) { + openFile(); + mNativeWindow.mEvents.mRedraw = true; + } + } + } + + private: + + tp::ualni fileContentSize(tp::File& file) { + return file.size() - fileContentStartIdx(); + } + + tp::ualni fileContentStartIdx() { + return logo_len; + } + + void readFile(tp::File& file) { + using namespace tp; + + // read cfg + file.read(&cfg.mCapacity); + cfg.mAllocationSizes = new tp::int4[cfg.mCapacity]; + cfg.mChunkLengths = new tp::int4[cfg.mCapacity]; + + file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity); + file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity); + + // read evens + tp::ualni events_length; + file.read(&events_length); + + // one pass to collect data about groups in 'flag' + auto events_adress = file.adress; + for (tp::ualni i = 0; i < events_length; i++) { + Event evnt; + file.read(&evnt); + auto idx = groups.presents(evnt.size); + Group* group = NULL; + if (idx) { + group = groups.getSlotVal(idx); + } else { + group = new Group(evnt.size); + groups.put(evnt.size, group); + } + group->flag++; + } + for (auto grp : groups) { + grp->val->events.reserve(grp->val->flag); + grp->val->flag = 0; + } + file.adress = events_adress; + + // read actual events + for (tp::ualni idx = 0; idx < events_length; idx++) { + Event evnt; + file.read(&evnt); + auto grp = groups.get(evnt.size); + grp->events[grp->flag] = evnt; + grp->flag++; + } + + // initialize additional structure for sorting + groups_sorted.reserve(groups.size()); + for (auto grp : groups) { + groups_sorted[grp.entry_idx] = grp->val; + } + + // generate timelines + for (auto grp : groups_sorted) { + grp.data()->timeline.recordEvents(grp.data()->events); + grp.data()->timeline.to_seconds(); + } + } + + void openFile() { + using namespace tp; + File log(filename.cstr(), osfile_openflags::LOAD); + + if (!log.opened) { + status = LoadStatus::INVALID_FILE_PATH; + return; + } + + log.read_bytes(logo_loaded, logo_len); + if (!tp::memequal(logo_loaded, logo, logo_len)) { + status = LoadStatus::INVALID_FILE_FORMAT; + return; + } + + log.Preload(); + + try { + readFile(log); + status = LoadStatus::DONE; + } catch (...) { + status = LoadStatus::INTERNAL_ERROR; + return; + } + } + + void HeaderDrawCallback() override { + using namespace ImGui; + Button("Analize"); + Button("Save To Header File"); + } + + void DrawCallback() { + + auto pos = this->mNativeWindow.mAppearence.mSize / 2; + if (status != LoadStatus::DONE) { + + nvgFillColor(mNvg, nvgRGB(250, 250, 250)); + nvgFontSize(mNvg, 24); + nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); + + switch (status) { + case LoadStatus::NONE: + nvgText(mNvg, pos.x, pos.y, "Loading...", 0); + break; + + case LoadStatus::INVALID_FILE_PATH: + nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0); + break; + + case LoadStatus::INVALID_FILE_FORMAT: + nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0); + break; + + case LoadStatus::INTERNAL_ERROR: + nvgText(mNvg, pos.x, pos.y, "Internal Error", 0); + } + return; + } + + drawEditor(); + } + + enum class GroupSorting { + LARGER_SIZE, + SMALLER_SIZE, + LARGER_TOTAL_SIZE, + SMALLER_TOTAL_SIZE, + } sorting = GroupSorting::LARGER_SIZE; + + void sortGroups(GroupSorting type) { + if (type == sorting) { + return; + } + + switch (type) { + case Memusage::GroupSorting::LARGER_SIZE: + groups_sorted.sort([](Group* const& i1, Group* const& i2) { + return i1->group_size > i2->group_size; + }); + break; + case Memusage::GroupSorting::SMALLER_SIZE: + groups_sorted.sort([](Group* const& i1, Group* const& i2) { + return i1->group_size < i2->group_size; + }); + break; + case Memusage::GroupSorting::LARGER_TOTAL_SIZE: + break; + case Memusage::GroupSorting::SMALLER_TOTAL_SIZE: + break; + default: + break; + } + + sorting = type; + } + + Group* mGroupActive = NULL; + + void groupView() { + using namespace ImGui; + int tmp; + + const char* names[] = { + {"Larger Group Size"}, + {"Smaller Group Size"}, + //{"LARGER_TOTAL_SIZE"}, + //{"SMALER_TOTAL_SIZE"}, + }; + + tmp = (int) sorting; + Combo(" ", &tmp, names, 2); + Separator(); + sortGroups((GroupSorting) tmp); + + for (auto grp : groups_sorted) { + if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) { + mGroupActive = grp.data(); + } + } + } + + void drawTimeline(const Timeline& timeline) { + if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) { + ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length()); + ImPlot::EndPlot(); + } + } + + void infoView() { + using namespace ImGui; + if (!mGroupActive) { + Text("No Group Selected"); + return; + } + + Text("Total Events : %i", mGroupActive->events.length()); + + Separator(); + + drawTimeline(mGroupActive->timeline); + } + + void drawEditor() { + using namespace ImGui; + + Begin("Groups View"); + groupView(); + End(); + + Begin("Info"); + infoView(); + End(); + } +}; + +int main(char argc, char* argv[]) { + + if (argc != 2) { + printf("invalid arguments"); + return 0; + } + + //ImPlot:: + + Memusage::InitializeTypes(); + + { + Memusage app(argv[1]); + + ImPlot::CreateContext(); + + app.run(); + + ImPlot::DestroyContext(); + + printf("internal error"); + } + + Memusage::UnInitializeTypes(); + + tp::terminate(); +} \ No newline at end of file diff --git a/tools/MemUsage/memusage.h b/tools/MemUsage/memusage.h new file mode 100644 index 0000000..4f7a4ba --- /dev/null +++ b/tools/MemUsage/memusage.h @@ -0,0 +1,401 @@ + + +#include "GuiWindow.h" + +#include "imgui.h" +#include "implot.h" +#include "nanovg.h" + +#include "strings.h" +#include "filesystem.h" +#include "array.h" +#include "list.h" +#include "map.h" + +#include "intersections.h" + +#include "pickalloc_analizer.h" +#include "pickalloc_cfg.h" + +#include + +class Memusage : public tp::GuiWindow { + + tp::string filename; + + static const char logo_len = 16; + const char logo[logo_len] = "memusage\0\0\0\0\0\0\0"; + char logo_loaded[logo_len]; + + PickAllocConfig cfg; + + enum class LoadStatus { + NONE, + DONE, + INVALID_FILE_PATH, + INVALID_FILE_FORMAT, + INTERNAL_ERROR, + } status; + + typedef tp::PickAllocDataAnalizer::Event Event; + typedef tp::alni SizeKey; + + struct Timeline { + tp::Array size; + tp::Array time; + tp::ualni peak = 0; + tp::ualni avereging = 0; + + void recordEvents(tp::Array& events) { + tp::ualni total_mem_usage = 0; + + size.extend(events.length()); + time.extend(events.length()); + + for (auto ev : events) { + + if (ev->dealloc) { + total_mem_usage -= ev->size; + } else { + total_mem_usage += ev->size; + } + + time[ev.idx()] = (tp::alnf) ev->time; + size[ev.idx()] = (tp::alnf) total_mem_usage; + + if (peak < total_mem_usage) { + peak = total_mem_usage; + } + } + + avereging = 1; + } + + void to_seconds() { + for (auto tm : time) { + tm.data() /= 1000; + } + } + }; + + // Contains events of fixed size + struct Group { + tp::ualni group_size = 0; + tp::Array events; + + tp::ualni flag = 0; + + Timeline timeline; + + Group(tp::ualni grp_size) : group_size(grp_size) {} + }; + + tp::HashMap groups; + tp::Array groups_sorted; + + public: + + Memusage(const char* filename) : GuiWindow() { + + this->mHeader.mAppTitle = "Mamusage Analizer"; + this->mHeader.mPadding = 10; + tp::string file_path = __FILE__; + tp::make_dir_str(file_path.get_writable()); + tp::make_dir_str(file_path.get_writable()); + this->mRscDirectory = file_path + "/"; + this->mGuiRootIsWindow = false; + LoadFonts(); + + this->filename = filename; + } + + ~Memusage() { + for (auto tl : groups) { + delete tl->val; + } + + if (cfg.mAllocationSizes) delete[] cfg.mAllocationSizes; + if (cfg.mChunkLengths) delete[] cfg.mChunkLengths; + } + + void run() { + + // display status + for (auto i : tp::Range(2)) { + procInputs(); + beginDraw(); + if (mNativeWindow.mEvents.mRedraw) { + DrawCallback(); + } + endDraw(); + mNativeWindow.mEvents.mRedraw = true; + } + + while (!mNativeWindow.mEvents.mTerminate) { + tp::Timer timer(5); + procInputs(); + beginDraw(); + if (mNativeWindow.mEvents.mRedraw) { + DrawCallback(); + } + endDraw(); + timer.wait(); + + if (status == LoadStatus::NONE) { + openFile(); + mNativeWindow.mEvents.mRedraw = true; + } + } + } + + private: + + tp::ualni fileContentSize(tp::File& file) { + return file.size() - fileContentStartIdx(); + } + + tp::ualni fileContentStartIdx() { + return logo_len; + } + + void readFile(tp::File& file) { + using namespace tp; + + // read cfg + file.read(&cfg.mCapacity); + cfg.mAllocationSizes = new tp::int4[cfg.mCapacity]; + cfg.mChunkLengths = new tp::int4[cfg.mCapacity]; + + file.read_bytes((tp::int1*)cfg.mAllocationSizes, sizeof(tp::int4) * cfg.mCapacity); + file.read_bytes((tp::int1*)cfg.mChunkLengths, sizeof(tp::int4) * cfg.mCapacity); + + // read evens + tp::ualni events_length; + file.read(&events_length); + + // one pass to collect data about groups in 'flag' + auto events_adress = file.adress; + for (tp::ualni i = 0; i < events_length; i++) { + Event evnt; + file.read(&evnt); + auto idx = groups.presents(evnt.size); + Group* group = NULL; + if (idx) { + group = groups.getSlotVal(idx); + } else { + group = new Group(evnt.size); + groups.put(evnt.size, group); + } + group->flag++; + } + for (auto grp : groups) { + grp->val->events.reserve(grp->val->flag); + grp->val->flag = 0; + } + file.adress = events_adress; + + // read actual events + for (tp::ualni idx = 0; idx < events_length; idx++) { + Event evnt; + file.read(&evnt); + auto grp = groups.get(evnt.size); + grp->events[grp->flag] = evnt; + grp->flag++; + } + + // initialize additional structure for sorting + groups_sorted.reserve(groups.size()); + for (auto grp : groups) { + groups_sorted[grp.entry_idx] = grp->val; + } + + // generate timelines + for (auto grp : groups_sorted) { + grp.data()->timeline.recordEvents(grp.data()->events); + grp.data()->timeline.to_seconds(); + } + } + + void openFile() { + using namespace tp; + File log(filename.cstr(), osfile_openflags::LOAD); + + if (!log.opened) { + status = LoadStatus::INVALID_FILE_PATH; + return; + } + + log.read_bytes(logo_loaded, logo_len); + if (!tp::memequal(logo_loaded, logo, logo_len)) { + status = LoadStatus::INVALID_FILE_FORMAT; + return; + } + + log.Preload(); + + try { + readFile(log); + status = LoadStatus::DONE; + } catch (...) { + status = LoadStatus::INTERNAL_ERROR; + return; + } + } + + void HeaderDrawCallback() override { + using namespace ImGui; + Button("Analize"); + Button("Save To Header File"); + } + + void DrawCallback() { + + auto pos = this->mNativeWindow.mAppearence.mSize / 2; + if (status != LoadStatus::DONE) { + + nvgFillColor(mNvg, nvgRGB(250, 250, 250)); + nvgFontSize(mNvg, 24); + nvgTextAlign(mNvg, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); + + switch (status) { + case LoadStatus::NONE: + nvgText(mNvg, pos.x, pos.y, "Loading...", 0); + break; + + case LoadStatus::INVALID_FILE_PATH: + nvgText(mNvg, pos.x, pos.y, "Invalid File Path", 0); + break; + + case LoadStatus::INVALID_FILE_FORMAT: + nvgText(mNvg, pos.x, pos.y, "Invalid File Format", 0); + break; + + case LoadStatus::INTERNAL_ERROR: + nvgText(mNvg, pos.x, pos.y, "Internal Error", 0); + } + return; + } + + drawEditor(); + } + + enum class GroupSorting { + LARGER_SIZE, + SMALLER_SIZE, + LARGER_TOTAL_SIZE, + SMALLER_TOTAL_SIZE, + } sorting = GroupSorting::LARGER_SIZE; + + void sortGroups(GroupSorting type) { + if (type == sorting) { + return; + } + + switch (type) { + case Memusage::GroupSorting::LARGER_SIZE: + groups_sorted.sort([](Group* const& i1, Group* const& i2) { + return i1->group_size > i2->group_size; + }); + break; + case Memusage::GroupSorting::SMALLER_SIZE: + groups_sorted.sort([](Group* const& i1, Group* const& i2) { + return i1->group_size < i2->group_size; + }); + break; + case Memusage::GroupSorting::LARGER_TOTAL_SIZE: + break; + case Memusage::GroupSorting::SMALLER_TOTAL_SIZE: + break; + default: + break; + } + + sorting = type; + } + + Group* mGroupActive = NULL; + + void groupView() { + using namespace ImGui; + int tmp; + + const char* names[] = { + {"Larger Group Size"}, + {"Smaller Group Size"}, + //{"LARGER_TOTAL_SIZE"}, + //{"SMALER_TOTAL_SIZE"}, + }; + + tmp = (int) sorting; + Combo(" ", &tmp, names, 2); + Separator(); + sortGroups((GroupSorting) tmp); + + for (auto grp : groups_sorted) { + if (Selectable(tp::string((tp::alni)grp.data()->group_size).cstr())) { + mGroupActive = grp.data(); + } + } + } + + void drawTimeline(const Timeline& timeline) { + if (ImPlot::BeginPlot(" Plot ", {-1, -1}, ImPlotFlags_CanvasOnly | ImPlotFlags_AntiAliased)) { + ImPlot::PlotLine("Memory Usage", timeline.time.buff(), timeline.size.buff(), (tp::halni) timeline.time.length()); + ImPlot::EndPlot(); + } + } + + void infoView() { + using namespace ImGui; + if (!mGroupActive) { + Text("No Group Selected"); + return; + } + + Text("Total Events : %i", mGroupActive->events.length()); + + Separator(); + + drawTimeline(mGroupActive->timeline); + } + + void drawEditor() { + using namespace ImGui; + + Begin("Groups View"); + groupView(); + End(); + + Begin("Info"); + infoView(); + End(); + } +}; + +int main(char argc, char* argv[]) { + + if (argc != 2) { + printf("invalid arguments"); + return 0; + } + + //ImPlot:: + + Memusage::InitializeTypes(); + + { + Memusage app(argv[1]); + + ImPlot::CreateContext(); + + app.run(); + + ImPlot::DestroyContext(); + + printf("internal error"); + } + + Memusage::UnInitializeTypes(); + + tp::terminate(); +} \ No newline at end of file