Skip to main content
2 of 3
edited tags
Vaillancourt
  • 16.4k
  • 17
  • 55
  • 61

Memory tracker allocates while outputting results

I've written a memory tracker for use in my game engine. I based it on this slide in a talk by Nicolai Josuttis.

One problem I'm having is that, during reporting, the implicit string conversion and output stream operator functions allocate. This results in calls to new. Since I've overloaded new the tracker tries to track the new allocations and ends up invalidating the results.

Is there a way to output the results without allocating?

Memory.hpp

#pragma once
//https://www.youtube.com/watch?v=e2ZQyYr0Oi0
//C++17 - The Best Features - Nicolai Josuttis [ACCU 2018]

#include "Engine/Core/BuildConfig.hpp"
#include "Engine/Core/ErrorWarningAssert.hpp"

//#include "Engine/Profiling/StackTrace.hpp"

#include <new>
#include <sstream>
#include <iostream>
#include <ostream>

class Memory {
public:

    struct status_t {
        std::size_t leaked_objs  = 0;
        std::size_t leaked_bytes = 0;
        operator bool() const noexcept {
            return leaked_objs || leaked_bytes;
        }
        operator std::string() const noexcept {
#ifdef TRACK_MEMORY
            std::ostringstream ss;
            std::string s = ss.str();
            ss << "Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
            return s;
#else
            return {};
#endif
        }
        friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_t& s) noexcept {
#ifdef TRACK_MEMORY
            os << "Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
#endif
            return os;
        }
    };
    struct status_frame_t {
        std::size_t frame_id = 0;
        std::size_t leaked_objs = 0;
        std::size_t leaked_bytes = 0;
        operator bool() const noexcept {
            return leaked_objs || leaked_bytes;
        }
        operator std::string() const noexcept {
#ifdef TRACK_MEMORY
            std::ostringstream ss;
            ss << "Frame " << frame_id << ": Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
            std::string s = ss.str();
            return s;
#else
            return {};
#endif
        }
        friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_frame_t& s) noexcept {
#ifdef TRACK_MEMORY
            os << "Frame " << s.frame_id << ": Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
#endif
            return os;
        }
    };

    [[nodiscard]] static void* allocate(std::size_t n) noexcept {
        if(is_enabled()) {
            ++frameCount;
            frameSize += n;
            ++allocCount;
            allocSize += n;
            if(maxSize < allocSize) {
                maxSize = allocSize;
            }
            if(maxCount < allocCount) {
                maxCount = allocCount;
            }
        }
        return std::malloc(n);
    }

    static void deallocate(void* ptr, std::size_t size) noexcept {
        if(is_enabled()) {
            ++framefreeCount;
            framefreeSize += size;
            ++freeCount;
            freeSize += size;
        }
        std::free(ptr);
    }

    static void enable([[maybe_unused]]bool e) noexcept {
#ifdef TRACK_MEMORY
        _active = e;
#endif
    }

    static bool is_enabled() noexcept {
#ifdef TRACK_MEMORY
        return _active;
#else
        return false;
#endif
    }

    static void trace([[maybe_unused]]bool doTrace) noexcept {
#ifdef TRACK_MEMORY
        _trace = doTrace;
#endif
    }

    static void tick() noexcept {
#ifdef TRACK_MEMORY
        if(auto f = Memory::frame_status()) {
            std::cout << f << '\n';
        }
        ++frameCounter;
        resetframecounters();
#endif
    }

    static void resetframecounters() noexcept {
#ifdef TRACK_MEMORY
        frameSize = 0;
        frameCount = 0;
        framefreeCount = 0;
        framefreeSize = 0;
#endif
    }

    static status_t status() noexcept {
        return { allocCount - freeCount, allocSize - freeSize };
    }

    static status_frame_t frame_status() noexcept {
        return { frameCounter, frameCount - framefreeCount, frameSize - framefreeSize };
    }

    inline static std::size_t maxSize = 0;
    inline static std::size_t maxCount = 0;
    inline static std::size_t allocSize = 0;
    inline static std::size_t allocCount = 0;
    inline static std::size_t frameSize = 0;
    inline static std::size_t frameCount = 0;
    inline static std::size_t frameCounter = 0;
    inline static std::size_t freeCount = 0;
    inline static std::size_t freeSize = 0;
    inline static std::size_t framefreeCount = 0;
    inline static std::size_t framefreeSize = 0;
protected:
private:
    inline static bool _active = false;
    inline static bool _trace = false;
};

#ifdef TRACK_MEMORY

void* operator new(std::size_t size);
void* operator new[](std::size_t size);
void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete[](void* ptr, std::size_t size) noexcept;

#endif

Memory.cpp

#include "Engine/Profiling/Memory.hpp"

#ifdef TRACK_MEMORY

void* operator new(std::size_t size) {
    return Memory::allocate(size);
}

void* operator new[](std::size_t size) {
    return Memory::allocate(size);
}

void operator delete(void* ptr, std::size_t size) noexcept {
    Memory::deallocate(ptr, size);
}

void operator delete[](void* ptr, std::size_t size) noexcept {
    Memory::deallocate(ptr, size);
}

#endif
Casey
  • 2.1k
  • 2
  • 18
  • 26