Compare commits
3 commits
master
...
allocator-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5954c4a15 | ||
|
|
a72a3646cd | ||
|
|
10c1566c6c |
22 changed files with 1649 additions and 193 deletions
15
Allocators/CMakeLists.txt
Normal file
15
Allocators/CMakeLists.txt
Normal file
|
|
@ -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})
|
||||||
36
Allocators/README.md
Normal file
36
Allocators/README.md
Normal file
|
|
@ -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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Also debug.memleaks binary will be generated in the working directory that can be viewed with MemLeaks Viewer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
21
Allocators/private/Allocators.cpp
Normal file
21
Allocators/private/Allocators.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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); }
|
||||||
76
Allocators/private/HeapAllocator.cpp
Normal file
76
Allocators/private/HeapAllocator.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
|
||||||
|
|
||||||
|
#include "HeapAllocator.hpp"
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
|
|
||||||
|
#include "PrivateConfig.hpp"
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
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
|
||||||
232
Allocators/private/HeapAllocatorGlobal.cpp
Normal file
232
Allocators/private/HeapAllocatorGlobal.cpp
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
|
||||||
|
|
||||||
|
#include "HeapAllocatorGlobal.hpp"
|
||||||
|
#include "PrivateConfig.hpp"
|
||||||
|
|
||||||
|
#include "AllocatorsTypes.hpp"
|
||||||
|
// #include "Callstack.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
using namespace tp;
|
||||||
|
|
||||||
|
#if not defined(MEM_DEBUG)
|
||||||
|
|
||||||
|
// ----------------------- Release Implementation ---------------------------- //
|
||||||
|
|
||||||
|
void* HeapAllocGlobal::allocate(ualni aBlockSize) { return malloc(aBlockSize); }
|
||||||
|
void HeapAllocGlobal::deallocate(void* aPtr) {
|
||||||
|
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
|
||||||
23
Allocators/public/Allocators.hpp
Normal file
23
Allocators/public/Allocators.hpp
Normal file
|
|
@ -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);
|
||||||
4
Allocators/public/AllocatorsTypes.hpp
Normal file
4
Allocators/public/AllocatorsTypes.hpp
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Environment.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
136
Allocators/public/ChunkAllocator.hpp
Normal file
136
Allocators/public/ChunkAllocator.hpp
Normal file
|
|
@ -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 <typename tType, ualni tNumBlocks>
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
}
|
||||||
26
Allocators/public/HeapAllocator.hpp
Normal file
26
Allocators/public/HeapAllocator.hpp
Normal file
|
|
@ -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() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
44
Allocators/public/HeapAllocatorGlobal.hpp
Normal file
44
Allocators/public/HeapAllocatorGlobal.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AllocatorsTypes.hpp"
|
||||||
|
|
||||||
|
// #include "Callstack.hpp"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
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() {}
|
||||||
|
};
|
||||||
|
}
|
||||||
161
Allocators/public/PoolAllocator.hpp
Normal file
161
Allocators/public/PoolAllocator.hpp
Normal file
|
|
@ -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 <typename tType, ualni tNumBlocks>
|
||||||
|
class PoolAlloc {
|
||||||
|
|
||||||
|
typedef ChunkAlloc<tType, tNumBlocks> 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
9
Allocators/public/PrivateConfig.hpp
Normal file
9
Allocators/public/PrivateConfig.hpp
Normal file
|
|
@ -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
|
||||||
249
Allocators/tests/Test.cpp
Normal file
249
Allocators/tests/Test.cpp
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
|
||||||
|
#include "UnitTest++/UnitTest++.h"
|
||||||
|
|
||||||
|
#include "Allocators.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
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 <alni tSize, class tAllocator>
|
||||||
|
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<alni> 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<alni> 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 <typename Alloc>
|
||||||
|
void testAlloc() {
|
||||||
|
try {
|
||||||
|
TestBenches<size, Alloc> heapTests{};
|
||||||
|
heapTests.runTests();
|
||||||
|
} catch (...) {
|
||||||
|
ASSERT(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SUITE(Allocators) {
|
||||||
|
TEST(GlobalHeap) { testAlloc<HeapAllocGlobal>(); }
|
||||||
|
|
||||||
|
TEST(Heap) { testAlloc<tp::HeapAlloc>(); }
|
||||||
|
|
||||||
|
TEST(Chunk) {
|
||||||
|
testAlloc<ChunkAlloc<TestStruct, size>>();
|
||||||
|
testAlloc<ChunkAlloc<TestStruct, size * 2>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Pool) {
|
||||||
|
testAlloc<PoolAlloc<TestStruct, 1>>();
|
||||||
|
testAlloc<PoolAlloc<TestStruct, size / 100>>();
|
||||||
|
testAlloc<PoolAlloc<TestStruct, size>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -12,24 +12,27 @@ include(cmake/ModulesOptions.txt)
|
||||||
|
|
||||||
set(WINDOWS_LIBRARIES "../moduleswindowsl" CACHE STRING "Svn repository with windows libraries https://svn.riouxsvn.com/moduleswindowsl")
|
set(WINDOWS_LIBRARIES "../moduleswindowsl" CACHE STRING "Svn repository with windows libraries https://svn.riouxsvn.com/moduleswindowsl")
|
||||||
|
|
||||||
include(cmake/FindGLEW.cmake)
|
#include(cmake/FindGLEW.cmake)
|
||||||
include(cmake/FindOIDN.cmake)
|
#include(cmake/FindOIDN.cmake)
|
||||||
include(cmake/FindPortAudio.cmake)
|
#include(cmake/FindPortAudio.cmake)
|
||||||
|
|
||||||
add_subdirectory(Externals)
|
#add_subdirectory(Externals)
|
||||||
|
|
||||||
add_subdirectory(Modules)
|
add_subdirectory(Modules)
|
||||||
add_subdirectory(Containers)
|
add_subdirectory(Callstack)
|
||||||
add_subdirectory(Math)
|
add_subdirectory(Allocators)
|
||||||
|
|
||||||
|
#add_subdirectory(Containers)
|
||||||
|
#add_subdirectory(Math)
|
||||||
# add_subdirectory(Language)
|
# add_subdirectory(Language)
|
||||||
add_subdirectory(Connection)
|
#add_subdirectory(Connection)
|
||||||
add_subdirectory(Graphics)
|
#add_subdirectory(Graphics)
|
||||||
add_subdirectory(DataAnalysis)
|
#add_subdirectory(DataAnalysis)
|
||||||
add_subdirectory(Objects)
|
#add_subdirectory(Objects)
|
||||||
add_subdirectory(Widgets)
|
#add_subdirectory(Widgets)
|
||||||
add_subdirectory(LibraryViewer)
|
#add_subdirectory(LibraryViewer)
|
||||||
add_subdirectory(RasterRender)
|
#add_subdirectory(RasterRender)
|
||||||
add_subdirectory(3DScene)
|
#add_subdirectory(3DScene)
|
||||||
add_subdirectory(RayTracer)
|
#add_subdirectory(RayTracer)
|
||||||
add_subdirectory(Sketch3D)
|
#add_subdirectory(Sketch3D)
|
||||||
add_subdirectory(3DEditor)
|
#add_subdirectory(3DEditor)
|
||||||
|
|
|
||||||
14
Callstack/CMakeLists.txt
Normal file
14
Callstack/CMakeLists.txt
Normal file
|
|
@ -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})
|
||||||
338
Callstack/private/Callstack.cpp
Normal file
338
Callstack/private/Callstack.cpp
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
|
||||||
|
#include "Callstack.hpp"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
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 <cstring>
|
||||||
|
#include <cxxabi.h>
|
||||||
|
#include <execinfo.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
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 <windows.h>
|
||||||
|
#include <dbghelp.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#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
|
||||||
105
Callstack/public/Callstack.hpp
Normal file
105
Callstack/public/Callstack.hpp
Normal file
|
|
@ -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<CallStackKey, CallStack*, DefaultAllocator, hashCallStack> mSnapshots;
|
||||||
|
Map<FramePointer, DebugSymbols> mSymbols;
|
||||||
|
};
|
||||||
|
}
|
||||||
44
Callstack/tests/Test.cpp
Normal file
44
Callstack/tests/Test.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
#include "UnitTest++/UnitTest++.h"
|
||||||
|
|
||||||
|
#include "Callstack.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
|
@ -32,5 +32,9 @@ target_link_libraries(testLinearRingBuffer ${PROJECT_NAME} gtest)
|
||||||
gtest_discover_tests(testLinearRingBuffer)
|
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)
|
add_executable(AVLTreeSpeedTest ./tests/AVLTreeProfiling.cpp)
|
||||||
target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME})
|
target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME})
|
||||||
|
|
|
||||||
|
|
@ -1,188 +1,50 @@
|
||||||
#include "LinearRingBuffer.hpp"
|
#include <benchmark/benchmark.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
// -------------------------------------------------------------
|
||||||
|
// Benchmark: push_back into a vector
|
||||||
#include <cstring>
|
// -------------------------------------------------------------
|
||||||
#include <cstdint>
|
static void BM_VectorPushBack(benchmark::State& state) {
|
||||||
#include <random>
|
for (auto _ : state) {
|
||||||
|
std::vector<int> v;
|
||||||
static void fill(void* ptr, size_t n, uint8_t value) { std::memset(ptr, value, n); }
|
v.reserve(state.range(0));
|
||||||
|
for (int i = 0; i < state.range(0); i++) {
|
||||||
static bool check(const void* ptr, size_t n, uint8_t value) {
|
v.push_back(i);
|
||||||
const uint8_t* p = static_cast<const uint8_t*>(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<const uint8_t*>(ptr);
|
|
||||||
for (size_t i = 0; i < n; ++i) {
|
|
||||||
if (p[i] != value) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Data = std::vector<uint8_t>;
|
|
||||||
using DataStorage = std::vector<Data>;
|
|
||||||
|
|
||||||
typedef ::testing::Types<
|
|
||||||
std::integral_constant<size_t, 64>,
|
|
||||||
std::integral_constant<size_t, 128>,
|
|
||||||
std::integral_constant<size_t, 1000>,
|
|
||||||
std::integral_constant<size_t, 4096>,
|
|
||||||
std::integral_constant<size_t, 4096 * 2>>
|
|
||||||
BufferSizes;
|
|
||||||
|
|
||||||
template <typename TypeParam>
|
|
||||||
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<size_t> 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::DoNotOptimize(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::memcpy(this->rb.write_data(), pushedSample.data(), pushedSample.size());
|
BENCHMARK(BM_VectorPushBack)->Arg(1000)->Arg(100000);
|
||||||
this->rb.write_advance(pushedSample.size());
|
|
||||||
|
|
||||||
if (storage) {
|
// -------------------------------------------------------------
|
||||||
storage->push_back(pushedSample);
|
// Benchmark: inserting into std::map
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
static void BM_MapInsert(benchmark::State& state) {
|
||||||
|
for (auto _ : state) {
|
||||||
|
std::map<int, int> m;
|
||||||
|
for (int i = 0; i < state.range(0); i++) {
|
||||||
|
m.emplace(i, i);
|
||||||
}
|
}
|
||||||
};
|
benchmark::DoNotOptimize(m);
|
||||||
|
|
||||||
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(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<int, int> m;
|
||||||
|
for (int i = 0; i < state.range(0); i++)
|
||||||
|
m.emplace(i, i);
|
||||||
|
|
||||||
std::vector<uint8_t> result;
|
for (auto _ : state) {
|
||||||
|
auto it = m.find(state.range(0) / 2);
|
||||||
result.reserve(historySize + current.size());
|
benchmark::DoNotOptimize(it);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BENCHMARK(BM_MapLookup)->Arg(1000)->Arg(100000);
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
BENCHMARK_MAIN();
|
||||||
::testing::InitGoogleTest(&argc, argv);
|
|
||||||
return RUN_ALL_TESTS();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
1
Externals/CMakeLists.txt
vendored
1
Externals/CMakeLists.txt
vendored
|
|
@ -56,3 +56,4 @@ target_include_directories(${PROJECT_NAME} INTERFACE ./imageIO/)
|
||||||
add_subdirectory(lalr)
|
add_subdirectory(lalr)
|
||||||
|
|
||||||
add_subdirectory(googletest)
|
add_subdirectory(googletest)
|
||||||
|
add_subdirectory(benchmark)
|
||||||
|
|
|
||||||
53
tools/gen_bench_image.py
Normal file
53
tools/gen_bench_image.py
Normal file
|
|
@ -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()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue