HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Memory allocation tracking using anonymous lambda wrapper

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
trackinganonymouswrapperusingmemoryallocationlambda

Problem

I had this idea to exploit anonymous lambdas that are immediately executed that wrap around an allocation function to track those allocations in a statically created Stats object.

This is the code I came up with after some time

```
#define TW_INLINE __attribute__((always_inline))

#include
#include

using u32 = std::uint32_t;
using cstring = const char*;

using namespace std;

struct MemoryBlock {};

template
TW_INLINE MemoryBlock twalloc(T& alloc_, u32 size_) {
// normally an alloc_.Allocate() would allocaty memory somehow, might be
// malloc for HeapAllocator I just stub it here
malloc(128);
return MemoryBlock{}; // this would hold allocation info
}

struct AllocationInfo
{
static AllocationInfo* s_head;
static AllocationInfo* s_last;

u32 allocationCount;
cstring file;
u32 line;
u32 allocationSize;
AllocationInfo* next;

AllocationInfo(cstring file_, u32 line_, u32 size_);
~AllocationInfo() = default;

static void DumpAllocationInfo();
};

template
TW_INLINE MemoryBlock _twalloc(T& alloc_, u32 size_) {
return twalloc(alloc_, size_);
}

#define twalloc(alloc, size) \
[](auto& alloc_, u32 size_, cstring file_, u32 line_) { \
static AllocationInfo info{ file_, line_, size_ }; \
++info.allocationCount; \
return _twalloc(alloc_, size_); \
}(alloc, size, __FILE__, __LINE__)

AllocationInfo* AllocationInfo::s_head = nullptr;

AllocationInfo* AllocationInfo::s_last = nullptr;

AllocationInfo::AllocationInfo(cstring file_, u32 line_, u32 size_)
: file(file_)
, line(line_)
, allocationSize(size_)
, allocationCount(0)
, next(nullptr)
{
if (!s_head) {
s_head = this;
s_last = s_head;
}
else

Solution

Your approach avoids any additional heap memory allocations for the allocation record structure, but I'm not convinced it is the best approach for the following reasons:

-
It will definitely bloat the executable image since each and every allocation call (executed or not) adds a static AllocationInfo instance to the prog data.

-
This is not thread safe, unless you make each AllocationInfo thread-local, which would further degrade performance. But actually, C++11 requires static variable initialization to be thread-safe, and we can see in the assembly output that the compiler generates calls to mutex locks before the constructor calls (__cxa_guard_acquire, __cxa_guard_release), but not around the allocationCount increment, so you're paying a performance cost already for partial thread-safety.

-
There is a subtle problem with this code. If a client of your API calls twalloc from a DLL and that DLL gets unloaded before the main process exits, then pointers to the static instances of AllocationInfo created inside the DLL will no longer be valid and the program would crash when trying to walk the memory blocks at the end of main.

Alternatives

Other ways of keeping the allocation records that you should explore, with pros and cons:

-
Append or prepend the records to the user block:

  • Takes no additional heap allocations and will only consume space once allocations are actually ran, adding only a small constant overhead per allocation.



  • Record data might get corrupted is the user stomps over your header/footer. This is actually quite common and the main reason to avoid this approach.



-
Store the records in a separate pool:

  • No risk of having the user stomping over your data.



  • Will only consume space once allocations are actually ran.



  • Records can be kept together in a linear pool, which might improve search and iteration due to better CPU cache usage.



  • Might require dynamically allocating a new chunk of memory, but this can be mitigated with the use of memory pools and simple recycling schemes.



  • Requires some form of lookup to associate a pointer to the corresponding allocation record (think of a hash-table), so there might be some overhead when deallocating a user block.



Thread safety and contention

Furthermore, you should try be avoid thread contention (locking) as much as possible. Since your allocation function already expects and allocator object, then you should keep the allocation records inside that allocator. This way each thread can declare is own private allocator and avoid the necessity of mutex locks altogether.

Context

StackExchange Code Review Q#141702, answer score: 2

Revisions (0)

No revisions yet.