From 4be94dd0066fd74460ae47228322d543a7617f48 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Fri, 23 Oct 2026 22:26:01 +0200 Subject: [PATCH] perf(debug): count what the program takes with new (global operator new/delete of the executable) and the objects of the model instances in the memory section of the debug panel --- rendering/opengl46/opengl46renderer.cpp | 9 +- utilities/memorystats.cpp | 131 ++++++++++++++++++++++++ utilities/memorystats.h | 10 ++ 3 files changed, 149 insertions(+), 1 deletion(-) diff --git a/rendering/opengl46/opengl46renderer.cpp b/rendering/opengl46/opengl46renderer.cpp index f72783fa..cc00ae9e 100644 --- a/rendering/opengl46/opengl46renderer.cpp +++ b/rendering/opengl46/opengl46renderer.cpp @@ -1174,6 +1174,10 @@ void opengl46_renderer::Update_Memory_Info() text += "process: " + mb( process.resident ) + " resident (peak " + mb( process.peak ) + ")" + ( process.committed > 0 ? ", " + mb( process.committed ) + " private" : std::string{} ) + "\n"; } + // what the program took with new: the rest of the private memory is of the drivers, libraries and the c runtime + auto const heap { query_heap_memory() }; + text += "taken with new: " + mb( heap.bytes ) + " in " + std::to_string( heap.blocks ) + " blocks" + + ( process.valid && ( process.committed > heap.bytes ) ? ", the rest of the private memory " + mb( process.committed - heap.bytes ) + " (drivers, libraries)" : std::string{} ) + "\n"; // the memory of the card, all processes: GL_NVX_gpu_memory_info (in kB) if( m_nvxmemory < 0 ) { m_nvxmemory = 0; @@ -1239,7 +1243,10 @@ void opengl46_renderer::Update_Memory_Info() } accounted += shapes.bytes + lines.bytes + sleepers.bytes; text += "scenery: " + counted( shapes, "shapes" ) + " " + mb( shapes.bytes ) + ", " + counted( lines, "lines" ) + " " + mb( lines.bytes ) + ", sleepers of " - + counted( sleepers, "tracks" ) + " " + mb( sleepers.bytes ) + ", " + std::to_string( instances ) + " model instances\n"; + + counted( sleepers, "tracks" ) + " " + mb( sleepers.bytes ) + "\n"; + // the objects of the model instances themselves, without the strings and the containers they hold + accounted += instances * sizeof( TAnimModel ); + text += "model instances: " + std::to_string( instances ) + ", their objects " + mb( instances * sizeof( TAnimModel ) ) + " (" + std::to_string( sizeof( TAnimModel ) ) + " bytes each)\n"; // sounds: the data of the buffers, which the AL keeps in the process auto const sounds { audio::renderer.buffers_memory() }; accounted += sounds.bytes; diff --git a/utilities/memorystats.cpp b/utilities/memorystats.cpp index d42c0bb9..a889c170 100644 --- a/utilities/memorystats.cpp +++ b/utilities/memorystats.cpp @@ -10,16 +10,147 @@ http://mozilla.org/MPL/2.0/. #include "stdafx.h" #include "utilities/memorystats.h" +#include +#include +#include + #if defined(_WIN32) +#include #include #pragma comment(lib, "psapi.lib") #elif defined(__APPLE__) #include +#include #else #include +#include #include #endif +// the global new and delete of the executable count the memory of the blocks they hand out (query_heap_memory()). the blocks come +// from malloc() as with the default operators; the aligned forms are left to the runtime +namespace +{ + +std::atomic g_heapbytes{0}; +std::atomic g_heapblocks{0}; + +std::size_t block_size(void *Block) +{ +#if defined(_WIN32) + return ::_msize(Block); +#elif defined(__APPLE__) + return ::malloc_size(Block); +#else + return ::malloc_usable_size(Block); +#endif +} + +void *counted_new(std::size_t Size) +{ + if (Size == 0) + { + Size = 1; + } + while (true) + { + if (auto *block{std::malloc(Size)}) + { + g_heapbytes.fetch_add(block_size(block), std::memory_order_relaxed); + g_heapblocks.fetch_add(1, std::memory_order_relaxed); + return block; + } + auto const handler{std::get_new_handler()}; + if (handler == nullptr) + { + throw std::bad_alloc(); + } + handler(); + } +} + +void counted_delete(void *Block) noexcept +{ + if (Block == nullptr) + { + return; + } + g_heapbytes.fetch_sub(block_size(Block), std::memory_order_relaxed); + g_heapblocks.fetch_sub(1, std::memory_order_relaxed); + std::free(Block); +} + +} // namespace + +void *operator new(std::size_t Size) +{ + return counted_new(Size); +} + +void *operator new[](std::size_t Size) +{ + return counted_new(Size); +} + +void *operator new(std::size_t Size, std::nothrow_t const &) noexcept +{ + try + { + return counted_new(Size); + } + catch (...) + { + return nullptr; + } +} + +void *operator new[](std::size_t Size, std::nothrow_t const &) noexcept +{ + try + { + return counted_new(Size); + } + catch (...) + { + return nullptr; + } +} + +void operator delete(void *Block) noexcept +{ + counted_delete(Block); +} + +void operator delete[](void *Block) noexcept +{ + counted_delete(Block); +} + +void operator delete(void *Block, std::size_t) noexcept +{ + counted_delete(Block); +} + +void operator delete[](void *Block, std::size_t) noexcept +{ + counted_delete(Block); +} + +void operator delete(void *Block, std::nothrow_t const &) noexcept +{ + counted_delete(Block); +} + +void operator delete[](void *Block, std::nothrow_t const &) noexcept +{ + counted_delete(Block); +} + +heap_memory query_heap_memory() +{ + return {g_heapbytes.load(std::memory_order_relaxed), g_heapblocks.load(std::memory_order_relaxed)}; +} + process_memory query_process_memory() { process_memory result; diff --git a/utilities/memorystats.h b/utilities/memorystats.h index 3a909add..d2ec3731 100644 --- a/utilities/memorystats.h +++ b/utilities/memorystats.h @@ -23,6 +23,16 @@ struct process_memory process_memory query_process_memory(); +// what the program took with new and hasn't given back with delete (the global operators of the executable count it): bytes of +// the blocks, as the c runtime made them, and blocks. what libraries, the c runtime and the drivers take otherwise isn't in it +struct heap_memory +{ + std::uint64_t bytes{0}; + std::uint64_t blocks{0}; +}; + +heap_memory query_heap_memory(); + // what a part of the program keeps in memory: things and bytes struct memory_count {