diff --git a/Allocators/CMakeLists.txt b/Allocators/CMakeLists.txt new file mode 100644 index 0000000..8b59432 --- /dev/null +++ b/Allocators/CMakeLists.txt @@ -0,0 +1,15 @@ +project(Allocators) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp") +file(GLOB HEADERS "./public/*.hpp") +add_library(${PROJECT_NAME} STATIC ${SOURCES} ${HEADERS}) +target_include_directories(${PROJECT_NAME} PUBLIC ./public/) +target_link_libraries(${PROJECT_NAME} PUBLIC Callstack) + +### -------------------------- Tests -------------------------- ### +enable_testing() +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(Tests${PROJECT_NAME} ${TEST_SOURCES}) +target_link_libraries(Tests${PROJECT_NAME} ${PROJECT_NAME} UnitTest++) +add_test(NAME Tests${PROJECT_NAME} COMMAND Tests${PROJECT_NAME}) \ No newline at end of file diff --git a/Allocators/README.md b/Allocators/README.md new file mode 100644 index 0000000..d2beeba --- /dev/null +++ b/Allocators/README.md @@ -0,0 +1,36 @@ +# 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::gModuleAllocators, NULL }; + tp::ModuleManifest TestModule("Test", NULL, NULL, ModuleDependencies); + TestModule.initialize(); + + test_call11(); + + TestModule.deinitialize(); +} + +``` +If memory leaks were detected it will be logged in the output console. + +![image](https://user-images.githubusercontent.com/63184036/222794298-3f238de4-c0b8-41fa-b7ec-c0c675da8f05.png) + +Also debug.memleaks binary will be generated in the working directory that can be viewed with MemLeaks Viewer. + +![image](https://user-images.githubusercontent.com/63184036/222793169-a405effe-72be-42fc-b375-bb06dce0a735.png) + diff --git a/Allocators/private/Allocators.cpp b/Allocators/private/Allocators.cpp new file mode 100644 index 0000000..ab98472 --- /dev/null +++ b/Allocators/private/Allocators.cpp @@ -0,0 +1,21 @@ + +#include "Allocators.hpp" +#include "HeapAllocatorGlobal.hpp" + +#include +#include + +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) noexcept { tp::HeapAllocGlobal::deallocate(aPtr); } +void operator delete[](void* aPtr) noexcept { 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 tp::HeapAllocGlobal::allocate(aSize); } +void* operator new[](size_t aSize, tp::HeapAllocGlobal& aAlloc) { return tp::HeapAllocGlobal::allocate(aSize); } +void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc) { tp::HeapAllocGlobal::deallocate(aPtr); } +void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc) { tp::HeapAllocGlobal::deallocate(aPtr); } \ No newline at end of file diff --git a/Allocators/private/HeapAllocator.cpp b/Allocators/private/HeapAllocator.cpp new file mode 100644 index 0000000..f4e1cdf --- /dev/null +++ b/Allocators/private/HeapAllocator.cpp @@ -0,0 +1,76 @@ + + +#include "HeapAllocator.hpp" +#include "HeapAllocatorGlobal.hpp" + +#include "PrivateConfig.hpp" + +#include + +using namespace tp; + +#if not defined(MEM_DEBUG) + +// ----------------------- Release Implementation ---------------------------- // +void* HeapAlloc::allocate(ualni aBlockSize) { return malloc(aBlockSize); } +void HeapAlloc::deallocate(void* aPtr) { + if (!aPtr) return; + free(aPtr); +} +HeapAlloc::~HeapAlloc() {} + +#else + +namespace tp { + struct MemHeadLocal { + MemHeadLocal* mPrev; + MemHeadLocal* mNext; + }; +} + +void* HeapAlloc::allocate(ualni aBlockSize) { + auto head = (MemHeadLocal*) HeapAllocGlobal::allocate(aBlockSize + sizeof(MemHeadLocal)); + auto out = head + 1; + + mNumAllocations++; + if (mEntry) { + DEBUG_ASSERT(!mEntry->mNext); + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { + head->mNext = nullptr; + head->mPrev = nullptr; + } + mEntry = head; + + return out; +} + +void HeapAlloc::deallocate(void* aPtr) { + if (!aPtr) return; + + auto head = ((MemHeadLocal*) (aPtr)) - 1; + + mNumAllocations--; + DEBUG_ASSERT(!mEntry->mNext); + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; + if (head == mEntry) { + mEntry = head->mPrev; + } + + HeapAllocGlobal::deallocate(head); +} + +HeapAlloc::~HeapAlloc() { + if (mNumAllocations) { + DEBUG_ASSERT(0 && "Destruction of not freed Allocator"); + +#ifdef MEM_STACK_TRACE + // TODO : log leaks and free them up +#endif + } +} + +#endif diff --git a/Allocators/private/HeapAllocatorGlobal.cpp b/Allocators/private/HeapAllocatorGlobal.cpp new file mode 100644 index 0000000..9d2a76e --- /dev/null +++ b/Allocators/private/HeapAllocatorGlobal.cpp @@ -0,0 +1,232 @@ + + +#include "HeapAllocatorGlobal.hpp" +#include "PrivateConfig.hpp" + +#include "AllocatorsTypes.hpp" +// #include "Callstack.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) { + if (!aPtr) return; + free(aPtr); +} +HeapAllocGlobal::~HeapAllocGlobal() = default; +bool HeapAllocGlobal::checkLeaks() { return false; } +void HeapAllocGlobal::startIgnore() {} +void HeapAllocGlobal::stopIgnore() {} +ualni HeapAllocGlobal::getNAllocations() { return 0; } + +#else + +tp::MemHead* tp::HeapAllocGlobal::mEntry = nullptr; +tp::ualni tp::HeapAllocGlobal::mNumAllocations = 0; +std::mutex tp::HeapAllocGlobal::mMutex; +bool tp::HeapAllocGlobal::mIgnore = true; +bool tp::HeapAllocGlobal::mEnableCallstack = true; + +#ifdef MEM_STACK_TRACE +tp::CallStackCapture tp::HeapAllocGlobal::mCallstack; +#endif + + // ----------------------- Debug Implementation ---------------------------- // +// |----------------| +// | MemHead | +// |----------------| +// | wrap top | +// |----------------| - Allocated Block Layout +// | data | +// |----------------| +// | wrap bottom | +// |----------------| + +namespace tp { + struct MemHead { + MemHead* mPrev; + MemHead* mNext; + uhalni mBlockSize; + uhalni mIgnored; +#ifdef MEM_STACK_TRACE + const CallStackCapture::CallStack* mCallStack; +#else + void* p; +#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 +ALLOCATE: + auto head = (MemHead*) malloc(aBlockSize + WRAP_SIZE * 2 + HEAD_SIZE); + if (!head) { + printf("WARNING : Cant allocate memory. Trying again\n"); + goto ALLOCATE; // Just freeze if no memory is available + } + + auto wrap_top = (int1*) (head + 1); + auto data = wrap_top + WRAP_SIZE; + auto wrap_bottom = data + aBlockSize; + + head->mBlockSize = aBlockSize; + head->mIgnored = mIgnore; + + // 2) Link with existing blocks + mMutex.lock(); + mNumAllocations++; + if (mEntry) { + DEBUG_ASSERT(mEntry->mNext == nullptr); + head->mNext = nullptr; + head->mPrev = mEntry; + mEntry->mNext = head; + } else { + head->mNext = nullptr; + head->mPrev = nullptr; + } + mEntry = head; + + // 3) Trace the stack +#ifdef MEM_STACK_TRACE + // check if somewhat decides to call new within static variable initialization + head->mCallStack = (mEnableCallstack && mCallstack.initialized) ? mCallstack.getSnapshot() : nullptr; +#endif + + mMutex.unlock(); + + // 4) Wrap fill + memSetVal(wrap_top, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_bottom, WRAP_SIZE, WRAP_VAL); + + // 5) clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data, aBlockSize, CLEAR_ALLOC_VAL); +#endif + + return data; +} + +void HeapAllocGlobal::deallocate(void* aPtr) { + if (!aPtr) return; + + // 1) Restore the pointers + auto head = ((MemHead*) ((int1*) aPtr - WRAP_SIZE)) - 1; + auto wrap_top = (int1*) (head + 1); + auto data = wrap_top + WRAP_SIZE; + auto wrap_bottom = data + head->mBlockSize; + + // 2) Unlink with blocks + mMutex.lock(); + mNumAllocations--; + DEBUG_ASSERT(!mEntry->mNext); + if (head->mNext) head->mNext->mPrev = head->mPrev; + if (head->mPrev) head->mPrev->mNext = head->mNext; + if (head == mEntry) { + mEntry = head->mPrev; + } + + if (!head->mIgnored) { + // 3) Check the wrap + if (memCompareVal(wrap_top, WRAP_SIZE, WRAP_VAL)) { +#ifdef MEM_STACK_TRACE + if (head->mCallStack) mCallstack.printSnapshot(head->mCallStack); +#endif + ASSERT(!"Allocated Block Wrap Corrupted!"); + } + + if (memCompareVal(wrap_bottom, WRAP_SIZE, WRAP_VAL)) { +#ifdef MEM_STACK_TRACE + if (head->mCallStack) mCallstack.printSnapshot(head->mCallStack); +#endif + ASSERT(!"Allocated Block Wrap Corrupted!"); + } + + // 4) clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data, head->mBlockSize, CLEAR_DEALLOC_VAL); +#endif + } + + mMutex.unlock(); + + // 5) free the block + free(head); +} + +bool HeapAllocGlobal::checkLeaks() { + ualni ignoredCount = 0; + for (auto iter = mEntry; iter; iter = iter->mPrev) { + ignoredCount += iter->mIgnored; + } + + // 1) Check for not deallocated memory + if (mNumAllocations && ignoredCount < mNumAllocations) { + +#ifdef MEM_STACK_TRACE + for (auto iter = mEntry; iter; iter = iter->mPrev) { + if (!iter->mIgnored && iter->mCallStack) mCallstack.printSnapshot(iter->mCallStack); + } +#endif + + printf(" Count : %llu", mNumAllocations - ignoredCount); + + ASSERT(!"Destruction of not freed Allocator"); + return true; + } + return false; +} + +void HeapAllocGlobal::startIgnore() { + mMutex.lock(); + mIgnore = true; + mMutex.unlock(); +} + +void HeapAllocGlobal::stopIgnore() { + mMutex.lock(); + mIgnore = false; + mMutex.unlock(); +} + +ualni HeapAllocGlobal::getNAllocations() { + return mNumAllocations; +} + +void HeapAllocGlobal::enableCallstack() { + mMutex.lock(); + mEnableCallstack = true; + mMutex.unlock(); +} + +void HeapAllocGlobal::disableCallstack() { + mMutex.lock(); + mEnableCallstack = false; + mMutex.unlock(); +} + +HeapAllocGlobal::~HeapAllocGlobal() = default; + +#endif diff --git a/Allocators/public/Allocators.hpp b/Allocators/public/Allocators.hpp new file mode 100644 index 0000000..b03dd39 --- /dev/null +++ b/Allocators/public/Allocators.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +#include "ChunkAllocator.hpp" +#include "HeapAllocator.hpp" +#include "HeapAllocatorGlobal.hpp" +#include "PoolAllocator.hpp" + +void* operator new(std::size_t aSize); +void* operator new[](std::size_t aSize); +void operator delete(void* aPtr) noexcept; +void operator delete[](void* aPtr) noexcept; + +void* operator new(std::size_t aSize, tp::HeapAlloc& aAlloc); +void* operator new[](std::size_t aSize, tp::HeapAlloc& aAlloc); +void operator delete(void* aPtr, tp::HeapAlloc& aAlloc); +void operator delete[](void* aPtr, tp::HeapAlloc& aAlloc); + +void* operator new(std::size_t aSize, tp::HeapAllocGlobal& aAlloc); +void* operator new[](std::size_t aSize, tp::HeapAllocGlobal& aAlloc); +void operator delete(void* aPtr, tp::HeapAllocGlobal& aAlloc); +void operator delete[](void* aPtr, tp::HeapAllocGlobal& aAlloc); \ No newline at end of file diff --git a/Allocators/public/AllocatorsTypes.hpp b/Allocators/public/AllocatorsTypes.hpp new file mode 100644 index 0000000..5d9dd23 --- /dev/null +++ b/Allocators/public/AllocatorsTypes.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include "Environment.hpp" +#include "Utils.hpp" diff --git a/Allocators/public/ChunkAllocator.hpp b/Allocators/public/ChunkAllocator.hpp new file mode 100644 index 0000000..6f20eb8 --- /dev/null +++ b/Allocators/public/ChunkAllocator.hpp @@ -0,0 +1,136 @@ +#pragma once + +/* +* 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 "AllocatorsTypes.hpp" +#include "HeapAllocatorGlobal.hpp" +#include "PrivateConfig.hpp" + +namespace tp { + + // Chunk Allocator + // Constant time allocations and de-allocations in any order. + // Memory blocks are fixed in size and number of blocks can not exceed given parameter. + template + class ChunkAlloc { + + enum : ualni { + ALIGNED_SIZE = ENV_ALNI_SIZE_B, + + WRAP_SIZE_ALN = MEM_WRAP_SIZE / 2, + 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, + }; + + static constexpr ualni dataSize() { + auto BLOCK_SIZE_BYTES = sizeof(tType); + auto BLOCK_SIZE_ALIGNED = BLOCK_SIZE_BYTES / ALIGNED_SIZE; + return BLOCK_SIZE_ALIGNED; + } + + static constexpr ualni blockSize() { + auto BLOCK_SIZE_BYTES = sizeof(tType); + auto BLOCK_SIZE = dataSize() + bool(BLOCK_SIZE_BYTES % ALIGNED_SIZE) + WRAP_SIZE_ALN * 2; + return BLOCK_SIZE; + } + + private: + ualni* mNextBlock; + ualni mNumFreeBlocks; + ualni mNumInitBlocks; + ualni mBuff[tNumBlocks * blockSize() * ALIGNED_SIZE]; + + public: + ChunkAlloc() { + mNumFreeBlocks = tNumBlocks; + mNumInitBlocks = 0; + mNextBlock = mBuff; + } + + ~ChunkAlloc() = default; // TODO : check for leaks + + public: + void* allocate(ualni) { + DEBUG_ASSERT(mNumFreeBlocks && "Out Of Memory"); + + // 1) PreInitialize blocks + if (mNumInitBlocks < tNumBlocks) { + mBuff[mNumInitBlocks * blockSize()] = (ualni) (mBuff + (mNumInitBlocks + 1) * blockSize()); + mNumInitBlocks++; + } + + // 2) Find free block and update next free block + auto data = mNextBlock; + mNextBlock = (ualni*) (*data); + mNumFreeBlocks--; + +#ifdef MEM_DEBUG + // 3) Fill Wrap and offset data + auto wrap_top = data; + auto wrap_bottom = data + WRAP_SIZE_ALN + dataSize(); + + memSetVal(wrap_top, WRAP_SIZE, WRAP_VAL); + memSetVal(wrap_bottom, WRAP_SIZE, WRAP_VAL); + +// 4) Clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(data + WRAP_SIZE_ALN, dataSize() * ALIGNED_SIZE, CLEAR_ALLOC_VAL); +#endif + + data += WRAP_SIZE_ALN; +#endif + + return data; + } + + void deallocate(void* aPtr) { + DEBUG_ASSERT(aPtr >= mBuff && aPtr < mBuff + tNumBlocks * blockSize()); + + auto block = (ualni*) aPtr; + +#ifdef MEM_DEBUG + // 3) Check Wrap and offset data + auto wrap_bottom = block + dataSize(); + auto wrap_top = block - WRAP_SIZE_ALN; + block = wrap_top; + + // 3) Check the wrap + ASSERT(!memCompareVal(wrap_top, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!"); + ASSERT(!memCompareVal(wrap_bottom, WRAP_SIZE, WRAP_VAL) && "Allocated Block Wrap Corrupted!"); + +// 4) Clear data +#ifdef MEM_CLEAR_ON_ALLOC + memSetVal(block, blockSize() * ALIGNED_SIZE, CLEAR_DEALLOC_VAL); +#endif + +#endif + + (*block) = (ualni) mNextBlock; + mNextBlock = block; + mNumFreeBlocks++; + } + + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + + public: + [[nodiscard]] bool isFull() const { return !mNumFreeBlocks; } + [[nodiscard]] bool isEmpty() const { return mNumFreeBlocks == tNumBlocks; } + [[nodiscard]] const ualni* getBuff() const { return mBuff; } + }; +} \ No newline at end of file diff --git a/Allocators/public/HeapAllocator.hpp b/Allocators/public/HeapAllocator.hpp new file mode 100644 index 0000000..d51035f --- /dev/null +++ b/Allocators/public/HeapAllocator.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +namespace tp { + + class HeapAlloc { + +#ifdef MEM_DEBUG + ualni mNumAllocations = 0; + struct MemHeadLocal* mEntry = nullptr; +#endif + + public: + HeapAlloc() = default; + ~HeapAlloc(); + + public: + void* allocate(ualni aBlockSize); + void deallocate(void* aPtr); + + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + }; +} diff --git a/Allocators/public/HeapAllocatorGlobal.hpp b/Allocators/public/HeapAllocatorGlobal.hpp new file mode 100644 index 0000000..d32a377 --- /dev/null +++ b/Allocators/public/HeapAllocatorGlobal.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include "AllocatorsTypes.hpp" + +// #include "Callstack.hpp" + +#include + +namespace tp { + + class HeapAllocGlobal { + +#ifdef MEM_DEBUG + static ualni mNumAllocations; + static struct MemHead* mEntry; + static std::mutex mMutex; + static bool mIgnore; + static bool mEnableCallstack; +#ifdef MEM_STACK_TRACE // Save stack on allocation call + static CallStackCapture mCallstack; +#endif +#endif + + public: + HeapAllocGlobal() = default; + ~HeapAllocGlobal(); + + public: + static void* allocate(ualni aBlockSize); + static void deallocate(void* aPtr); + + static bool checkLeaks(); + static void startIgnore(); + static void stopIgnore(); + static ualni getNAllocations(); + + static void enableCallstack(); + static void disableCallstack(); + + public: + [[nodiscard]] bool checkWrap() const { return false; } + void checkValid() {} + }; +} diff --git a/Allocators/public/PoolAllocator.hpp b/Allocators/public/PoolAllocator.hpp new file mode 100644 index 0000000..1b53267 --- /dev/null +++ b/Allocators/public/PoolAllocator.hpp @@ -0,0 +1,161 @@ +#pragma once + +/* +Implementation: +* Adding chunk pointer to each chunk to form one-directional list that keeps track of free chunk +* Storing ordered pointers to chunks in order to find desired chunk from delete pointer on de-allocation in log time +* +* Allocations: +* 1) allocate with chunk stored in list entry +* 2) ... +* +* De-allocations: +* 1) binary-search with delete pointer to find desired chunk +* 2) ... +* +*/ + +#include "ChunkAllocator.hpp" + +namespace tp { + + // Pool Allocator + // Overcomes chunk allocator fixed number of max allocations + template + class PoolAlloc { + + typedef ChunkAlloc Chunk; + + struct Chunks { + + void add(Chunk* aChunk) { + + if (!mBuff) { + mLen = 16; + mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen); + mUsedLen = 1; + mBuff[0] = aChunk; + return; + } + + // ensure order + auto smaller_address = findUtil(mBuff, mBuff + mUsedLen, aChunk); + for (auto iter = mBuff + mUsedLen; iter != smaller_address; iter--) { + *iter = *(iter - 1); + } + + *(smaller_address) = aChunk; + mUsedLen++; + + // check for buff overflow + if (mUsedLen == mLen) { + auto prevBuff = mBuff; + mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen * 2); + memCopy(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); + mLen *= 2; + HeapAllocGlobal::deallocate(prevBuff); + } + } + + void remove(Chunk** del_address) { + if (mUsedLen == 1) { + mLen = 0; + mUsedLen = 0; + HeapAllocGlobal::deallocate(mBuff); + mBuff = nullptr; + return; + } + + // ensure order + for (auto iter = del_address; iter != mBuff + mUsedLen - 1; iter++) { + *iter = *(iter + 1); + } + + mUsedLen--; + + // check for buff low usage + if ((halnf) mUsedLen / (halnf) mLen < 0.25f) { + auto prevBuff = mBuff; + mBuff = (Chunk**) HeapAllocGlobal::allocate(sizeof(Chunk*) * mLen / 2); + memCopy(mBuff, prevBuff, sizeof(Chunk*) * mUsedLen); + mLen /= 2; + HeapAllocGlobal::deallocate(prevBuff); + } + } + + [[nodiscard]] Chunk** find(void* aPtr) { return findUtil(mBuff, mBuff + mUsedLen, aPtr) - 1; } + + [[nodiscard]] Chunk* findNotFull() const { + for (ualni idx = 0; idx < mUsedLen; idx++) { + if (!mBuff[idx]->isFull()) { + return mBuff[idx]; + } + } + return nullptr; + } + + Chunk** mBuff = nullptr; + ualni mUsedLen = 0; + ualni mLen = 0; + + private: + Chunk** findUtil(Chunk** aLeft, Chunk** aRight, void* aPtr) { + auto range = ualni(aRight - aLeft); + if (range == 1) { + return (aPtr < *aLeft) ? aLeft : aRight; + } + auto middle = aLeft + range / 2; + return (aPtr >= (*middle)) ? findUtil(middle, aRight, aPtr) : findUtil(aLeft, middle, aPtr); + } + }; + + private: + Chunks mChunks; + Chunk* mFreeChunk = nullptr; + + public: + PoolAlloc() = default; + ~PoolAlloc() = default; + + public: + void* allocate(ualni) { + if (!mFreeChunk || mFreeChunk->isFull()) { + auto new_free_chunk = mChunks.findNotFull(); + if (!new_free_chunk) { + new_free_chunk = new (HeapAllocGlobal::allocate(sizeof(Chunk))) Chunk(); + DEBUG_ASSERT(new_free_chunk); + mChunks.add(new_free_chunk); + } + mFreeChunk = new_free_chunk; + } + return mFreeChunk->allocate(0); + } + + void deallocate(void* aPtr) { + if (!aPtr) return; + auto chunk = mChunks.find(aPtr); + (*chunk)->deallocate(aPtr); + if ((*chunk)->isEmpty()) { + if (mFreeChunk == *chunk) mFreeChunk = nullptr; + HeapAllocGlobal::deallocate(*chunk); + mChunks.remove(chunk); + } + } + + public: + [[nodiscard]] bool checkWrap() const { return false; } + + void checkValid() { + return; + for (auto i = 0; i < mChunks.mUsedLen; i++) { + for (auto j = 0; j < mChunks.mUsedLen; j++) { + if (i > j) { + ASSERT(mChunks.mBuff[i] > mChunks.mBuff[j]); + } else if (i < j) { + ASSERT(mChunks.mBuff[i] < mChunks.mBuff[j]); + } + } + } + } + }; +} diff --git a/Allocators/public/PrivateConfig.hpp b/Allocators/public/PrivateConfig.hpp new file mode 100644 index 0000000..e8d735e --- /dev/null +++ b/Allocators/public/PrivateConfig.hpp @@ -0,0 +1,9 @@ +#pragma once + +#define MEM_WRAP_SIZE 8 // Wrapping Size in aligned units +#define MEM_WRAP_FILL_VAL 0xBB // Wrapping Fill Value +#define MEM_CLEAR_ON_ALLOC // Clear data on allocation +#define MEM_CLEAR_ON_DEALLOC // Clear data on free +#define MEM_CLEAR_ON_DEALLOC_VAL 0xAA // Clear data on free +#define MEM_CLEAR_ON_ALLOC_VAL 0xCC // Clear data on free +#define MEM_STACK_TRACE_MAX_DEPTH 32 // Call stack max depth \ No newline at end of file diff --git a/Allocators/tests/Test.cpp b/Allocators/tests/Test.cpp new file mode 100644 index 0000000..4b0563d --- /dev/null +++ b/Allocators/tests/Test.cpp @@ -0,0 +1,249 @@ + +#include "UnitTest++/UnitTest++.h" + +#include "Allocators.hpp" +#include "Utils.hpp" + +#include + +using namespace tp; + +struct TestStruct { + alni val = 0; + + TestStruct() : + val(0) {} + explicit TestStruct(alni val) : + val(val) {} + TestStruct(const TestStruct& in) : + val(in.val) {} + + ~TestStruct() { val = -1; } + + bool operator==(const TestStruct& in) const { return in.val == val; } +}; + +template +class TestBenches { + + tAllocator mAlloc{}; + TestStruct mData[tSize]{}; + TestStruct* mLoaded[tSize]{}; + bool mIsLoaded[tSize]{}; + alni mLoadedNum = 0; + +public: + TestBenches() { + for (alni i = 0; i < tSize; i++) { + mData[i].val = i; + mIsLoaded[i] = false; + mLoaded[i] = nullptr; + } + } + + void runTests() { + try { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + } catch (...) { + ASSERT(false) + } + } + +private: + alni randomIdx(bool state, Range range = { 0, tSize }) { + RAND: + auto idx = alni(alnf(range.idxBegin()) + randomFloat() * alnf(range.idxDiff() + 1)); + idx = clamp(idx, alni(0), tSize - 1); + if (state == mIsLoaded[idx]) goto RAND; + return idx; + } + + void verifyIntegrity() { + mAlloc.checkValid(); + ASSERT(!mAlloc.checkWrap()) + for (alni i = 0; i < tSize; i++) { + if (mIsLoaded[i]) { + ASSERT(*mLoaded[i] == mData[i]) + } + } + } + + void loadItem(alni idx) { + if (mIsLoaded[idx]) return; + verifyIntegrity(); + mLoaded[idx] = new (mAlloc.allocate(sizeof(TestStruct))) TestStruct(mData[idx]); + ASSERT(mLoaded[idx]); + mIsLoaded[idx] = true; + mLoadedNum++; + verifyIntegrity(); + } + + void unloadItem(alni idx) { + if (!mIsLoaded[idx]) return; + verifyIntegrity(); + mLoaded[idx]->~TestStruct(); + mAlloc.deallocate(mLoaded[idx]); + mIsLoaded[idx] = false; + mLoadedNum--; + verifyIntegrity(); + } + + void changeStates(Range rg, bool load, bool reversed = false, bool random = false) { + for (auto i : rg) { + alni idx = i; + if (random) { + idx = randomIdx(load, rg); + } else if (reversed) { + idx = rg.idxEnd() - i - 1; + } + (load) ? loadItem(idx) : unloadItem(idx); + } + } + + // full down-up load then up-down unload + void test1() { + changeStates({ 0, tSize }, true); + changeStates({ 0, tSize }, false, true); + } + + // full down-up load then down-up unload + void test2() { + changeStates({ 0, tSize }, true); + changeStates({ 0, tSize }, false); + } + + // full random load then random unload + void test3() { + changeStates({ 0, tSize }, true, false, true); + changeStates({ 0, tSize }, false, false, true); + } + + // combo tests 1-3 + void test4() { + test1(); + test1(); + + test2(); + test2(); + + test3(); + test3(); + } + + static alnf sineUpFunction(alnf aSize, alnf aX, bool aReverse) { + alnf end = 4 * 3.14159; + alnf a = (2 / 7.f) * aSize; + alnf b = end / aSize; + + alni c = ((-1 * aReverse) + (1 * !aReverse)); + alnf c1 = (aX - (end * aReverse)) / b; + alnf c2 = (a * sin(aX - (end * aReverse))); + alnf out = c1 + c2; + return (alnf) c * out; + } + + // sin load & sin unload with ~1/2 drop factor + void test5() { + alnf end = 4 * 3.14159; + alnf step = end / 4.f; + + for (char i = 0; i < 2; i++) { + for (alnf x = 0; x <= end; x += step) { + + alni target_alloc_count = (alni) ceil(sineUpFunction(tSize, x, i)); + target_alloc_count = clamp(target_alloc_count, alni(0), tSize); + + while (mLoadedNum > target_alloc_count) { + unloadItem(randomIdx(0)); + } + while (mLoadedNum < target_alloc_count) { + loadItem(randomIdx(1)); + } + } + } + } + + void checkWrap(ualni offset, bool after) { + offset = clamp(offset, (ualni) 1, (ualni) MEM_WRAP_SIZE); + + TestStruct* ts = mLoaded[randomIdx(0)]; + ualni shift = (sizeof(TestStruct) * after) + (offset - 1) * after - offset * (!after); + uint1* address = (((uint1*) ts) + shift); + + uint1 val = *address; + *address = 5; + + ASSERT(!mAlloc.checkWrap()); + + *address = val; + } + + // mem guards test + void test6() { + changeStates({ 0, tSize }, 1); +#ifdef MEM_DEBUG + for (alni after = 0; after < 2; after++) { + for (alni offset = 1; offset <= MEM_WRAP_SIZE; offset++) { + checkWrap(offset, after); + } + } +#endif + changeStates({ 0, tSize }, 0); + } +}; + +const ualni size = 500; + +template +void testAlloc() { + try { + TestBenches heapTests{}; + heapTests.runTests(); + } catch (...) { + ASSERT(false); + } +} + +SUITE(Allocators) { + TEST(GlobalHeap) { testAlloc(); } + + TEST(Heap) { testAlloc(); } + + TEST(Chunk) { + testAlloc>(); + testAlloc>(); + } + + TEST(Pool) { + testAlloc>(); + testAlloc>(); + testAlloc>(); + } + + TEST(Simple) { + auto a = new TestStruct(-1); + delete a; + } +} + + +int main() { + + tp::ModuleManifest* deps[] = { &tp::gModuleAllocators, nullptr }; + tp::ModuleManifest testModule("AllocatorsTest", nullptr, nullptr, deps); + + if (!testModule.initialize()) { + return 1; + } + + bool res = UnitTest::RunAllTests(); + + testModule.deinitialize(); + + return res; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index bf93755..9f257cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,24 +12,27 @@ include(cmake/ModulesOptions.txt) set(WINDOWS_LIBRARIES "../moduleswindowsl" CACHE STRING "Svn repository with windows libraries https://svn.riouxsvn.com/moduleswindowsl") -include(cmake/FindGLEW.cmake) -include(cmake/FindOIDN.cmake) -include(cmake/FindPortAudio.cmake) +#include(cmake/FindGLEW.cmake) +#include(cmake/FindOIDN.cmake) +#include(cmake/FindPortAudio.cmake) -add_subdirectory(Externals) +#add_subdirectory(Externals) add_subdirectory(Modules) -add_subdirectory(Containers) -add_subdirectory(Math) +add_subdirectory(Callstack) +add_subdirectory(Allocators) + +#add_subdirectory(Containers) +#add_subdirectory(Math) # add_subdirectory(Language) -add_subdirectory(Connection) -add_subdirectory(Graphics) -add_subdirectory(DataAnalysis) -add_subdirectory(Objects) -add_subdirectory(Widgets) -add_subdirectory(LibraryViewer) -add_subdirectory(RasterRender) -add_subdirectory(3DScene) -add_subdirectory(RayTracer) -add_subdirectory(Sketch3D) -add_subdirectory(3DEditor) +#add_subdirectory(Connection) +#add_subdirectory(Graphics) +#add_subdirectory(DataAnalysis) +#add_subdirectory(Objects) +#add_subdirectory(Widgets) +#add_subdirectory(LibraryViewer) +#add_subdirectory(RasterRender) +#add_subdirectory(3DScene) +#add_subdirectory(RayTracer) +#add_subdirectory(Sketch3D) +#add_subdirectory(3DEditor) diff --git a/Callstack/CMakeLists.txt b/Callstack/CMakeLists.txt new file mode 100644 index 0000000..fc7035c --- /dev/null +++ b/Callstack/CMakeLists.txt @@ -0,0 +1,14 @@ +project(Callstack) + +### ---------------------- Static Library --------------------- ### +file(GLOB SOURCES "./private/*.cpp") + +add_library(${PROJECT_NAME} STATIC ${SOURCES}) +target_include_directories(${PROJECT_NAME} PUBLIC ./public/) +target_link_libraries(${PROJECT_NAME} PUBLIC Modules) + +### -------------------------- Tests -------------------------- ### +file(GLOB TEST_SOURCES "./tests/*.cpp") +add_executable(Tests${PROJECT_NAME} ${TEST_SOURCES}) +target_link_libraries(Tests${PROJECT_NAME} ${PROJECT_NAME} UnitTest++) +add_test(NAME Tests${PROJECT_NAME} COMMAND Tests${PROJECT_NAME}) \ No newline at end of file diff --git a/Callstack/private/Callstack.cpp b/Callstack/private/Callstack.cpp new file mode 100644 index 0000000..4897748 --- /dev/null +++ b/Callstack/private/Callstack.cpp @@ -0,0 +1,338 @@ + +#include "Callstack.hpp" + +#include + +using namespace tp; + +// frame to reference on no sapce left +static void errorNoSpaceLeft() { + // to disable optimization of function + static int dummy; + dummy = 10; +} + +ualni CallStackCapture::CallStack::getDepth() const { + ualni len = 0; + for (long long frame : frames) { + if (!frame) { + break; + } + len++; + } + return len++; +} + +ualni CallStackCapture::hashCallStack(CallStackKey key) { + auto const cs = key.cs; + ualni out = 0; + for (ualni i = 0; cs->frames[i]; i++) { + out += cs->frames[i]; + } + return out; +} + +bool CallStackCapture::CallStackKey::operator==(const CallStackCapture::CallStackKey& in) const { + for (ualni i = 0; i < MAX_CALL_DEPTH_CAPTURE; i++) { + if (cs->frames[i] != in.cs->frames[i]) { + return false; + } + if (cs->frames[i] == 0 && in.cs->frames[i] == 0) { + return true; + } + } + DEBUG_ASSERT(0 && "Must Not Happen") + return true; +} + +CallStackCapture::CallStackCapture() { + static_assert(MAX_CALL_DEPTH_CAPTURE >= 1); + static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB > 0); + static_assert(MAX_DEBUG_INFO_LEN > sizeof("unresolved")); + static_assert(MAX_CALL_CAPTURES_MEM_SIZE_MB * 1024 * 1024 > sizeof(CallStack)); + + mBuffLoad = 0; + mBuffLen = STACKS_LENGTH; + mBuff = (CallStack*) malloc(mBuffLen * sizeof(CallStack)); + + mErrorSnapshot.frames[0] = (alni) & errorNoSpaceLeft; + + + platformInit(); + + initialized = true; +} + +const CallStackCapture::CallStack* CallStackCapture::getSnapshot() { + + if (mBuffLoad > mBuffLen) { + return &mErrorSnapshot; + } + + CallStack* cs = &mBuff[mBuffLoad]; + + platformWriteStackTrace(cs); + + auto idx = mSnapshots.presents({ cs }); + if (idx) { + return mSnapshots.getSlotVal(idx); + } + + mSnapshots.put({ cs }, cs); + + mBuffLoad++; + return cs; +} + +const CallStackCapture::DebugSymbols* CallStackCapture::getSymbols(FramePointer frame) { + auto idx = mSymbols.presents(frame); + if (idx) { + return &mSymbols.getSlotVal(idx); + } + + mSymbols.put(frame, {}); + auto symbols = &mSymbols.get(frame); + + platformWriteDebugSymbols(frame, symbols); + return symbols; +} + +void CallStackCapture::clear() { + mBuffLoad = 0; + mSnapshots.removeAll(); + mSymbols.removeAll(); +} + +CallStackCapture::~CallStackCapture() { + free(mBuff); + initialized = false; +} + +// ---------------------------------- Platform Depended ---------------------------------- // + +#if defined(ENV_OS_LINUX) + +#include +#include +#include +#include + +void CallStackCapture::platformInit() { +} + +void CallStackCapture::platformDeinit() { +} + +void CallStackCapture::platformWriteStackTrace(CallStack* stack) { + auto depth = backtrace((void**) stack->frames, (int) MAX_CALL_DEPTH_CAPTURE - 1); + stack->frames[depth] = 0; +} + +static void getGetSourceFromBinaryAddress(const char* binary, const char* address, char* file, ualni* line) { + static char buff[1024]; + + snprintf(buff, sizeof(buff), "addr2line -e %s -a %s", binary, address); + FILE* pipe = popen(buff, "r"); + if (pipe) { + fgets(buff, sizeof(buff), pipe); + fgets(buff, sizeof(buff), pipe); + pclose(pipe); + char* linePtr = strchr(buff, ':'); + + printf("%s\n", buff); + + if (linePtr != nullptr && buff[0] != '?' && buff[1] != '?' && buff[2] != ':') { + *linePtr = '\0'; + auto sourceLen = std::strlen(buff); + std::strcpy(file, buff + ((sourceLen > CallStackCapture::MAX_DEBUG_INFO_LEN) ? (sourceLen - CallStackCapture::MAX_DEBUG_INFO_LEN) : 0)); + *line = strtoul(linePtr + 1, nullptr, 10); + return; + } + } + + std::strcpy(file, "unresolved"); + *line = 0; +} + +static void getDemangledName(const char* func, char* out) { + int status; + size_t funcDemangledSize = CallStackCapture::MAX_DEBUG_INFO_LEN; + char* funcDemangled = (char*) malloc(funcDemangledSize); + char* ret = abi::__cxa_demangle(func, funcDemangled, &funcDemangledSize, &status); + if (status == 0) { + funcDemangled = ret; + auto funcLen = std::strlen(funcDemangled); + std::strcpy(out, funcDemangled + ((funcLen > CallStackCapture::MAX_DEBUG_INFO_LEN) ? (funcLen - CallStackCapture::MAX_DEBUG_INFO_LEN) : 0)); + free(ret); + return; + } + auto funcLen = std::strlen(func); + std::strcpy(out, func + ((funcLen > CallStackCapture::MAX_DEBUG_INFO_LEN) ? (funcLen - CallStackCapture::MAX_DEBUG_INFO_LEN) : 0)); + free(funcDemangled); +} + +void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) { + void* addrList[1] = { (void*) frame }; + auto symbolsArray = backtrace_symbols(addrList, 1); + + // 'bin(fun+addr)' + char* bin = *symbolsArray; + char* func = nullptr; + char* offset = nullptr; + + // 'bin fun+addr' + for (char* p = bin; *p; ++p) { + if (*p == '(') { + *p = 0; + func = p + 1; + } else if (*p == '+') { + offset = p; + } else if (*p == ')' && offset) { + *p = 0; + } + } + + if (func && offset) { + getGetSourceFromBinaryAddress(bin, func, out->file, &out->line); + + if (offset != func) { + *offset = 0; + getDemangledName(func, out->function); + } else { + std::strcpy(out->function, "unresolved"); + } + } else { + std::strcpy(out->file, "unresolved"); + std::strcpy(out->function, "unresolved"); + } + + free(symbolsArray); +} + +void CallStackCapture::printSnapshot(const CallStack* snapshot) { + printf("CallStack: \n"); + if (snapshot) { + for (auto frame : *snapshot) { + auto symbols = getSymbols(frame.getFrame()); + printf(" %s ----- %s:%llu\n", symbols->getFunc(), symbols->getFile(), symbols->getLine()); + } + } + printf("\n"); +} + +void CallStackCapture::logAll() { + for (auto cs : *this) { + printSnapshot(cs.getCallStack()); + } +} + +#else +#include +#include +#include + +#pragma comment(lib, "dbghelp.lib") + +void CallStackCapture::platformInit() { + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); +} + +void CallStackCapture::platformDeinit() { + HANDLE process = GetCurrentProcess(); + SymCleanup(process); +} + +void CallStackCapture::platformWriteStackTrace(CallStack* stack) { + DWORD hash; + CONTEXT context; + memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_FULL; + RtlCaptureContext(&context); + + STACKFRAME64 stackFrame; + memset(&stackFrame, 0, sizeof(STACKFRAME64)); +#ifdef _M_IX86 + hash = IMAGE_FILE_MACHINE_I386; + stackFrame.AddrPC.Offset = context.Eip; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Ebp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Esp; + stackFrame.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + hash = IMAGE_FILE_MACHINE_AMD64; + stackFrame.AddrPC.Offset = context.Rip; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Rbp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Rsp; + stackFrame.AddrStack.Mode = AddrModeFlat; +#endif + + for (int frameIndex = 0; frameIndex < MAX_CALL_DEPTH_CAPTURE; ++frameIndex) { + if (!StackWalk64( + hash, + GetCurrentProcess(), + GetCurrentThread(), + &stackFrame, + &context, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL + )) { + + stack->frames[frameIndex] = 0; + break; + } + stack->frames[frameIndex] = (alni) (void*) stackFrame.AddrPC.Offset; + } + stack->frames[MAX_CALL_DEPTH_CAPTURE - 1] = 0; +} + +void CallStackCapture::platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out) { + HANDLE process = GetCurrentProcess(); + + IMAGEHLP_LINE64 lineInfo; + DWORD displacement; + lineInfo.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + if (SymGetLineFromAddr64(process, (DWORD64) frame, &displacement, &lineInfo)) { + strcpy(out->file, lineInfo.FileName); + out->line = lineInfo.LineNumber; + } else { + strcpy(out->file, "unresolved"); + out->line = 0; + } + + DWORD64 displacementSym = 0; + char symbolBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_PATH] = { 0 }; + IMAGEHLP_SYMBOL64* symbol = (IMAGEHLP_SYMBOL64*) symbolBuffer; + symbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + symbol->MaxNameLength = MAX_PATH; + + if (SymGetSymFromAddr64(process, (DWORD64) frame, &displacementSym, symbol)) { + strcpy(out->function, symbol->Name); + } else { + strcpy(out->function, "unresolved"); + } +} + +void CallStackCapture::printSnapshot(const CallStack* snapshot) { + printf("CallStack: \n"); + if (snapshot) { + for (int i = 0; i < snapshot->getDepth(); ++i) { + DebugSymbols symbols; + platformWriteDebugSymbols(snapshot->frames[i], &symbols); + printf(" %s ----- %s:%lu\n", symbols.function, symbols.file, symbols.line); + } + } + printf("\n"); +} + +void CallStackCapture::logAll() { + for (auto cs : *this) { + printSnapshot(cs.getCallStack()); + } +} +#endif \ No newline at end of file diff --git a/Callstack/public/Callstack.hpp b/Callstack/public/Callstack.hpp new file mode 100644 index 0000000..aac0e3c --- /dev/null +++ b/Callstack/public/Callstack.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include "Environment.hpp" +#include "Map.hpp" + +namespace tp { + + class CallStackCapture { + public: + typedef tp::alni FramePointer; + + bool initialized = false; + + enum { + MAX_CALL_DEPTH_CAPTURE = 16, + MAX_CALL_CAPTURES_MEM_SIZE_MB = 32, + MAX_DEBUG_INFO_LEN = 64, + }; + + class CallStack { + friend CallStackCapture; + FramePointer frames[MAX_CALL_DEPTH_CAPTURE]; + + public: + [[nodiscard]] ualni getDepth() const; + + class Iterator { + const FramePointer* mFrame; + + public: + explicit Iterator(const FramePointer* frame) : mFrame(frame){}; + FramePointer getFrame() { return *mFrame; } + bool operator==(const Iterator& in) const { return in.mFrame == mFrame; } + void operator++() { mFrame++; } + const Iterator& operator*() const { return *this; } + }; + + [[nodiscard]] Iterator begin() const { return Iterator(frames); } + [[nodiscard]] Iterator end() const { return Iterator(frames + getDepth()); } + }; + + enum { STACKS_LENGTH = (MAX_CALL_CAPTURES_MEM_SIZE_MB * 1024 * 1024) / sizeof(CallStack) }; + + class DebugSymbols { + friend CallStackCapture; + char function[MAX_DEBUG_INFO_LEN + 1] = { 0 }; + char file[MAX_DEBUG_INFO_LEN + 1] = { 0 }; + ualni line = 0; + + public: + [[nodiscard]] const char* getFunc() const { return function; } + [[nodiscard]] const char* getFile() const { return file; } + [[nodiscard]] ualni getLine() const { return line; } + }; + + public: + CallStackCapture(); + ~CallStackCapture(); + + [[nodiscard]] const CallStack* getSnapshot(); + const DebugSymbols* getSymbols(FramePointer fp); + + void printSnapshot(const CallStack* snapshot); + void logAll(); + + public: + class Iterator { + const CallStack* mSnapshot; + + public: + explicit Iterator(const CallStack* start) : mSnapshot(start) {}; + const CallStack* getCallStack() { return mSnapshot; } + bool operator==(const Iterator& in) const { return in.mSnapshot == mSnapshot; } + void operator++() { mSnapshot++; } + const Iterator& operator*() const { return *this; } + }; + + [[nodiscard]] Iterator begin() const { return Iterator(mBuff); } + [[nodiscard]] Iterator end() const { return Iterator(mBuff + mBuffLoad); } + + private: + struct CallStackKey { + CallStack* cs; + bool operator==(const CallStackKey& in) const; + }; + + static void platformWriteStackTrace(CallStack* stack); + static void platformWriteDebugSymbols(FramePointer frame, DebugSymbols* out); + [[nodiscard]] static ualni hashCallStack(CallStackKey key); + void platformInit(); + void platformDeinit(); + + void clear(); + + private: + CallStack mErrorSnapshot; + + ualni mBuffLen; + ualni mBuffLoad; + CallStack* mBuff; + + Map mSnapshots; + Map mSymbols; + }; +} diff --git a/Callstack/tests/Test.cpp b/Callstack/tests/Test.cpp new file mode 100644 index 0000000..e5ed3b7 --- /dev/null +++ b/Callstack/tests/Test.cpp @@ -0,0 +1,44 @@ + +#include "UnitTest++/UnitTest++.h" + +#include "Callstack.hpp" + +#include + +using namespace tp; + +void common(CallStackCapture& cs) { auto tmp = cs.getSnapshot(); } + +void first(CallStackCapture& cs) { + common(cs); + common(cs); + common(cs); +} + +void second(CallStackCapture& cs) { + common(cs); + common(cs); + common(cs); + common(cs); +} + +void third(CallStackCapture& cs) { + common(cs); + common(cs); +} + +void root(CallStackCapture& cs) { + first(cs); + second(cs); + third(cs); +} + +SUITE(Utils) { + TEST(CallStackCapture) { + CallStackCapture callstack; + root(callstack); + callstack.logAll(); + } +} + +int main() { return UnitTest::RunAllTests(); } \ No newline at end of file diff --git a/Containers/CMakeLists.txt b/Containers/CMakeLists.txt index 146ae01..dcdcff9 100644 --- a/Containers/CMakeLists.txt +++ b/Containers/CMakeLists.txt @@ -32,5 +32,9 @@ target_link_libraries(testLinearRingBuffer ${PROJECT_NAME} gtest) gtest_discover_tests(testLinearRingBuffer) +add_executable(benchLinearRingBuffer ./tests/benchLinearRingBuffer.cpp) +target_link_libraries(benchLinearRingBuffer benchmark::benchmark benchmark::benchmark_main ${PROJECT_NAME}) +# target_compile_features(benchLinearRingBuffer PRIVATE cxx_std_20) + add_executable(AVLTreeSpeedTest ./tests/AVLTreeProfiling.cpp) target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME}) diff --git a/Containers/tests/benchLinearRingBuffer.cpp b/Containers/tests/benchLinearRingBuffer.cpp index a9293f3..1c6fa43 100644 --- a/Containers/tests/benchLinearRingBuffer.cpp +++ b/Containers/tests/benchLinearRingBuffer.cpp @@ -1,188 +1,50 @@ -#include "LinearRingBuffer.hpp" +#include +#include +#include -#include - -#include -#include -#include - -static void fill(void* ptr, size_t n, uint8_t value) { std::memset(ptr, value, n); } - -static bool check(const void* ptr, size_t n, uint8_t value) { - const uint8_t* p = static_cast(ptr); - for (size_t i = 0; i < n; ++i) { - if (p[i] != value) return false; - } - return true; -} - -static bool expect_mem_eq(const void* ptr, size_t n, uint8_t value) { - const uint8_t* p = static_cast(ptr); - for (size_t i = 0; i < n; ++i) { - if (p[i] != value) return false; - } - return true; -} - -using Data = std::vector; -using DataStorage = std::vector; - -typedef ::testing::Types< - std::integral_constant, - std::integral_constant, - std::integral_constant, - std::integral_constant, - std::integral_constant> - BufferSizes; - -template -class LinearRingBufferTest : public ::testing::Test { -protected: - LinearRingBuffer rb; - - LinearRingBufferTest() : - historySize_(10), - rb(size_t(TypeParam::value), size_t(historySize_)) {} - - static constexpr auto ITERATIONS = TypeParam::value * 10; - - size_t messagesPushed_ = 0; - size_t historySize_ = 0; - - auto iterations() { return ITERATIONS; } - - auto historySize() { return historySize_; } - - size_t randomSize() { - static std::random_device rnd; - std::uniform_int_distribution dist(0, 1000); - return dist(rnd); - } - - auto write_and_advance(size_t messageSize, DataStorage* storage) { - Data pushedSample(messageSize); - - for (auto idx = 0; idx < messageSize; idx++) { - pushedSample[idx] = (idx + messagesPushed_++ * 53) % 321; +// ------------------------------------------------------------- +// Benchmark: push_back into a vector +// ------------------------------------------------------------- +static void BM_VectorPushBack(benchmark::State& state) { + for (auto _ : state) { + std::vector v; + v.reserve(state.range(0)); + for (int i = 0; i < state.range(0); i++) { + v.push_back(i); } + benchmark::DoNotOptimize(v); + } +} - std::memcpy(this->rb.write_data(), pushedSample.data(), pushedSample.size()); - this->rb.write_advance(pushedSample.size()); +BENCHMARK(BM_VectorPushBack)->Arg(1000)->Arg(100000); - if (storage) { - storage->push_back(pushedSample); +// ------------------------------------------------------------- +// Benchmark: inserting into std::map +// ------------------------------------------------------------- +static void BM_MapInsert(benchmark::State& state) { + for (auto _ : state) { + std::map m; + for (int i = 0; i < state.range(0); i++) { + m.emplace(i, i); } - }; - - auto read_and_advance(size_t messageSize, DataStorage* storage) { - Data poppedSample(messageSize); - - std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); - - this->rb.read_advance(poppedSample.size()); - - if (storage) { - storage->push_back(poppedSample); - } - }; - - auto reset_with_history() { this->rb.read_reset_with_history(); }; - - auto read_with_history(size_t messageSize, DataStorage* storage) { - Data poppedSample(messageSize); - - std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); - - if (storage) { - storage->push_back(poppedSample); - } - }; -}; - -TYPED_TEST_SUITE(LinearRingBufferTest, BufferSizes); - -TYPED_TEST(LinearRingBufferTest, LinearWriteAcrossBoundary) { - constexpr size_t N = TypeParam::value; - - if (N % 4096 != 0) { - return; - } - - auto* base = this->rb.write_data(); - - size_t half = N - 16; - fill(base, half, 0xAA); - fill(base + half, 32, 0xBB); - - EXPECT_EQ(expect_mem_eq(base, 16, 0xBB), true); - EXPECT_EQ(expect_mem_eq(base + 16, half - 16, 0xAA), true); - EXPECT_EQ(expect_mem_eq(base + half, 32, 0xBB), true); -} - -TYPED_TEST(LinearRingBufferTest, Chase) { - - DataStorage pushed; - DataStorage popped; - - static constexpr auto MSG_SIZE = 31; - - for (auto iteration = 0; iteration < this->iterations(); iteration++) { - this->write_and_advance(MSG_SIZE, &pushed); - this->read_and_advance(MSG_SIZE, &popped); - } - - ASSERT_EQ(pushed.size(), popped.size()); - - for (auto idx = 0; idx < popped.size(); idx++) { - ASSERT_EQ(pushed[idx], popped[idx]); + benchmark::DoNotOptimize(m); } } +BENCHMARK(BM_MapInsert)->Arg(1000)->Arg(100000); -Data makeMessageWithHistory(const Data& prev, const Data& current, size_t historySize) { - if (historySize > prev.size()) historySize = prev.size(); +// ------------------------------------------------------------- +// Benchmark: lookup in std::map +// ------------------------------------------------------------- +static void BM_MapLookup(benchmark::State& state) { + std::map m; + for (int i = 0; i < state.range(0); i++) + m.emplace(i, i); - std::vector result; - - result.reserve(historySize + current.size()); - - result.insert(result.end(), prev.end() - historySize, prev.end()); - result.insert(result.end(), current.begin(), current.end()); - - return result; -} - -TYPED_TEST(LinearRingBufferTest, History) { - - DataStorage pushed; - DataStorage popped; - - pushed.push_back(Data(this->historySize())); - - static constexpr auto MAX_MSG_SIZE = 111; - - for (auto iteration = 0; iteration < this->iterations(); iteration++) { - - const auto MSG_SIZE = this->historySize() + this->randomSize() % MAX_MSG_SIZE; - - auto prevPushed = pushed.back(); - pushed.clear(); - - this->reset_with_history(); - - this->write_and_advance(MSG_SIZE, &pushed); - auto currentPushed = pushed.back(); - - this->read_with_history(this->historySize() + MSG_SIZE, &popped); - auto poppedWithHistory = popped.back(); - popped.clear(); - - auto correctResult = makeMessageWithHistory(prevPushed, currentPushed, this->historySize()); - - ASSERT_EQ(correctResult, poppedWithHistory); + for (auto _ : state) { + auto it = m.find(state.range(0) / 2); + benchmark::DoNotOptimize(it); } } +BENCHMARK(BM_MapLookup)->Arg(1000)->Arg(100000); -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +BENCHMARK_MAIN(); diff --git a/Externals/CMakeLists.txt b/Externals/CMakeLists.txt index ec85993..3cf7daa 100644 --- a/Externals/CMakeLists.txt +++ b/Externals/CMakeLists.txt @@ -56,3 +56,4 @@ target_include_directories(${PROJECT_NAME} INTERFACE ./imageIO/) add_subdirectory(lalr) add_subdirectory(googletest) +add_subdirectory(benchmark) diff --git a/tools/gen_bench_image.py b/tools/gen_bench_image.py new file mode 100644 index 0000000..cd82078 --- /dev/null +++ b/tools/gen_bench_image.py @@ -0,0 +1,53 @@ +import json +import sys +import re +import matplotlib.pyplot as plt + +def load_bench(path): + with open(path, "r") as f: + return json.load(f)["benchmarks"] + +def parse_arg_from_name(name): + # Example: "BM_VectorPushBack/1000" -> 1000 + m = re.search(r"/(\d+)$", name) + return int(m.group(1)) if m else None + +def group_by_prefix(benchmarks): + groups = {} + for b in benchmarks: + name = b["name"] + prefix = name.split("/")[0] # BM_VectorPushBack + arg = parse_arg_from_name(name) # number after slash + if arg is None: + continue + if prefix not in groups: + groups[prefix] = {"x": [], "y": []} + groups[prefix]["x"].append(arg) + groups[prefix]["y"].append(b["real_time"]) + return groups + +def main(): + if len(sys.argv) != 2: + print("Usage: python3 plot_bench.py results.json") + return + + benchmarks = load_bench(sys.argv[1]) + groups = group_by_prefix(benchmarks) + + plt.figure(figsize=(10, 6)) + + for prefix, data in groups.items(): + xs = data["x"] + ys = data["y"] + plt.plot(xs, ys, marker="o", label=prefix) + + plt.xlabel("Input size (Arg)") + plt.ylabel("Real time (ns)") + plt.title("Google Benchmark Results") + plt.grid(True) + plt.legend() + plt.tight_layout() + plt.show() + +if __name__ == "__main__": + main()