Some memory tracer fixes/features

If range checks are enabled:
- Trace all objects
- Check if memory usage never gets negative
- Check if memory usage is 0 in destructor
- Track total memory usage (use pyngcore.GetTotalMemory())
This commit is contained in:
Matthias Hochsteger 2024-12-02 19:35:49 +01:00
parent 9935d877cc
commit 32e0026128
9 changed files with 126 additions and 53 deletions

View File

@ -687,6 +687,7 @@ namespace ngcore
size_t allocsize; size_t allocsize;
/// that's the data we have to delete, nullptr for not owning the memory /// that's the data we have to delete, nullptr for not owning the memory
T * mem_to_delete; T * mem_to_delete;
MemoryTracer mt;
using FlatArray<T,IndexType>::size; using FlatArray<T,IndexType>::size;
@ -708,6 +709,7 @@ namespace ngcore
{ {
allocsize = asize; allocsize = asize;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*asize);
} }
@ -717,7 +719,10 @@ namespace ngcore
{ {
allocsize = asize; allocsize = asize;
if(ownMemory) if(ownMemory)
{
mem_to_delete = adata; mem_to_delete = adata;
mt.Alloc(sizeof(T)*asize);
}
else else
mem_to_delete = nullptr; mem_to_delete = nullptr;
} }
@ -733,8 +738,7 @@ namespace ngcore
NETGEN_INLINE Array (Array && a2) NETGEN_INLINE Array (Array && a2)
{ {
mt.Swap(0., a2.mt, sizeof(T) * a2.allocsize); mt = std::move(a2.mt);
size = a2.size; size = a2.size;
data = a2.data; data = a2.data;
allocsize = a2.allocsize; allocsize = a2.allocsize;
@ -753,6 +757,7 @@ namespace ngcore
{ {
allocsize = size; allocsize = size;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*size);
for (size_t i = 0; i < size; i++) for (size_t i = 0; i < size; i++)
data[i] = a2.data[i]; data[i] = a2.data[i];
} }
@ -772,6 +777,7 @@ namespace ngcore
{ {
allocsize = size; allocsize = size;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*size);
/* /*
for (size_t i = 0; i < size; i++) for (size_t i = 0; i < size; i++)
data[i] = a2[i]; data[i] = a2[i];
@ -788,6 +794,7 @@ namespace ngcore
{ {
allocsize = size; allocsize = size;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*size);
size_t cnt = 0; size_t cnt = 0;
for (auto val : list) for (auto val : list)
data[cnt++] = val; data[cnt++] = val;
@ -800,6 +807,7 @@ namespace ngcore
{ {
allocsize = size; allocsize = size;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*size);
for(size_t i = 0; i < a2.Size(); i++) for(size_t i = 0; i < a2.Size(); i++)
data[i] = a2[i]; data[i] = a2[i];
for (size_t i = a2.Size(), j=0; i < size; i++,j++) for (size_t i = a2.Size(), j=0; i < size; i++,j++)
@ -1011,8 +1019,7 @@ namespace ngcore
/// steal array /// steal array
NETGEN_INLINE Array & operator= (Array && a2) NETGEN_INLINE Array & operator= (Array && a2)
{ {
mt.Swap(sizeof(T)*allocsize, a2.mt, sizeof(T)*a2.allocsize); mt = std::move(a2.mt);
ngcore::Swap (size, a2.size); ngcore::Swap (size, a2.size);
ngcore::Swap (data, a2.data); ngcore::Swap (data, a2.data);
ngcore::Swap (allocsize, a2.allocsize); ngcore::Swap (allocsize, a2.allocsize);
@ -1086,8 +1093,7 @@ namespace ngcore
NETGEN_INLINE void Swap (Array & b) NETGEN_INLINE void Swap (Array & b)
{ {
mt.Swap(sizeof(T) * allocsize, b.mt, sizeof(T) * b.allocsize); mt = std::move(b.mt);
ngcore::Swap (size, b.size); ngcore::Swap (size, b.size);
ngcore::Swap (data, b.data); ngcore::Swap (data, b.data);
ngcore::Swap (allocsize, b.allocsize); ngcore::Swap (allocsize, b.allocsize);
@ -1096,6 +1102,7 @@ namespace ngcore
NETGEN_INLINE void StartMemoryTracing () const NETGEN_INLINE void StartMemoryTracing () const
{ {
if(mem_to_delete)
mt.Alloc(sizeof(T) * allocsize); mt.Alloc(sizeof(T) * allocsize);
} }
@ -1105,7 +1112,6 @@ namespace ngcore
/// resize array, at least to size minsize. copy contents /// resize array, at least to size minsize. copy contents
NETGEN_INLINE void ReSize (size_t minsize); NETGEN_INLINE void ReSize (size_t minsize);
MemoryTracer mt;
}; };
@ -1158,6 +1164,7 @@ namespace ngcore
using Array<T>::allocsize; using Array<T>::allocsize;
using Array<T>::data; using Array<T>::data;
using Array<T>::mem_to_delete; using Array<T>::mem_to_delete;
using Array<T>::mt;
// using Array<T>::ownmem; // using Array<T>::ownmem;
public: public:
@ -1171,6 +1178,7 @@ namespace ngcore
data = new T[asize]; data = new T[asize];
allocsize = size; allocsize = size;
mem_to_delete = data; mem_to_delete = data;
mt.Alloc(sizeof(T)*asize);
} }
} }
@ -1191,6 +1199,7 @@ namespace ngcore
ArrayMem(ArrayMem && a2) ArrayMem(ArrayMem && a2)
: Array<T> (a2.Size(), (T*)mem) : Array<T> (a2.Size(), (T*)mem)
{ {
mt = std::move(a2.mt);
if (a2.mem_to_delete) if (a2.mem_to_delete)
{ {
mem_to_delete = a2.mem_to_delete; mem_to_delete = a2.mem_to_delete;
@ -1233,6 +1242,7 @@ namespace ngcore
ArrayMem & operator= (ArrayMem && a2) ArrayMem & operator= (ArrayMem && a2)
{ {
mt = std::move(a2.mt);
ngcore::Swap (mem_to_delete, a2.mem_to_delete); ngcore::Swap (mem_to_delete, a2.mem_to_delete);
ngcore::Swap (allocsize, a2.allocsize); ngcore::Swap (allocsize, a2.allocsize);
ngcore::Swap (size, a2.size); ngcore::Swap (size, a2.size);

View File

@ -40,12 +40,13 @@ namespace ngcore
if (owns_data) if (owns_data)
{ {
delete [] data; delete [] data;
mt.Free(Addr(size)+1); mt.Free(GetMemoryUsage());
} }
size = asize; size = asize;
data = new unsigned char [Addr (size)+1]; data = new unsigned char [Addr (size)+1];
mt.Alloc(Addr(size)+1); owns_data = true;
mt.Alloc(GetMemoryUsage());
} }
BitArray & BitArray :: Set () throw() BitArray & BitArray :: Set () throw()

View File

@ -49,6 +49,7 @@ public:
{ {
ba2.owns_data = false; ba2.owns_data = false;
ba2.data = nullptr; ba2.data = nullptr;
mt = std::move(ba2.mt);
} }
template <typename T> template <typename T>
@ -59,13 +60,17 @@ public:
int cnt = 0; int cnt = 0;
for (auto i = list.begin(); i < list.end(); i++, cnt++) for (auto i = list.begin(); i < list.end(); i++, cnt++)
if (*i) SetBit(cnt); if (*i) SetBit(cnt);
StartMemoryTracing();
} }
/// delete data /// delete data
~BitArray () ~BitArray ()
{ {
if (owns_data) if (owns_data)
{
delete [] data; delete [] data;
mt.Free(GetMemoryUsage());
}
} }
/// Set size, loose values /// Set size, loose values
@ -150,11 +155,11 @@ public:
NGCORE_API auto * Data() const { return data; } NGCORE_API auto * Data() const { return data; }
const size_t GetMemoryUsage() const { return owns_data ? (size+CHAR_BIT-1)/CHAR_BIT : 0; }
const MemoryTracer& GetMemoryTracer() const { return mt; } const MemoryTracer& GetMemoryTracer() const { return mt; }
void StartMemoryTracing() const void StartMemoryTracing() const
{ {
if(owns_data) mt.Alloc(GetMemoryUsage());
mt.Alloc(Addr(size)+1);
} }
private: private:

View File

@ -50,7 +50,7 @@ namespace ngcore
RangeException :: RangeException (// const std::string & where, RangeException :: RangeException (// const std::string & where,
const char * where, const char * where,
int ind, int imin, int imax) : Exception("") ptrdiff_t ind, ptrdiff_t imin, ptrdiff_t imax) : Exception("")
{ {
std::stringstream str; std::stringstream str;
str << where << ": index " << ind << " out of range [" << imin << "," << imax << ")\n"; str << where << ": index " << ind << " out of range [" << imin << "," << imax << ")\n";
@ -59,7 +59,7 @@ namespace ngcore
} }
void ThrowRangeException(const char * s, int ind, int imin, int imax) void ThrowRangeException(const char * s, ptrdiff_t ind, ptrdiff_t imin, ptrdiff_t imax)
{ {
throw RangeException(s, ind, imin, imax); throw RangeException(s, ind, imin, imax);
} }
@ -75,7 +75,7 @@ namespace ngcore
} }
void ThrowNotTheSameException(const char * s, long int a, long int b) void ThrowNotTheSameException(const char * s, ptrdiff_t a, ptrdiff_t b)
{ {
throw ngcore::Exception(std::string(s) + ", a="+ToString(a) + ", b="+ToString(b) + GetBackTrace()); throw ngcore::Exception(std::string(s) + ", a="+ToString(a) + ", b="+ToString(b) + GetBackTrace());
} }

View File

@ -1,6 +1,7 @@
#ifndef NETGEN_CORE_EXCEPTION_HPP #ifndef NETGEN_CORE_EXCEPTION_HPP
#define NETGEN_CORE_EXCEPTION_HPP #define NETGEN_CORE_EXCEPTION_HPP
#include <cstddef>
#include <sstream> // for stringstream #include <sstream> // for stringstream
#include <stdexcept> // for exception #include <stdexcept> // for exception
#include <string> // for string #include <string> // for string
@ -66,7 +67,7 @@ namespace ngcore
/// where it occurs, index, minimal and maximal indices /// where it occurs, index, minimal and maximal indices
RangeException (// const std::string & where, RangeException (// const std::string & where,
const char * where, const char * where,
int ind, int imin, int imax); ptrdiff_t ind, ptrdiff_t imin, ptrdiff_t imax);
/* /*
: Exception("") : Exception("")
{ {
@ -85,8 +86,8 @@ namespace ngcore
} }
}; };
[[noreturn]] NGCORE_API void ThrowRangeException(const char * s, int ind, int imin, int imax); [[noreturn]] NGCORE_API void ThrowRangeException(const char * s, ptrdiff_t ind, ptrdiff_t imin, ptrdiff_t imax);
[[noreturn]] NGCORE_API void ThrowNotTheSameException(const char * s, long int a, long int b); [[noreturn]] NGCORE_API void ThrowNotTheSameException(const char * s, ptrdiff_t a, ptrdiff_t b);
// Exception used if no simd implementation is available to fall back to standard evaluation // Exception used if no simd implementation is available to fall back to standard evaluation
@ -98,12 +99,12 @@ namespace ngcore
constexpr operator bool() const { return false; } }; constexpr operator bool() const { return false; } };
namespace detail { namespace detail {
template <typename T> template <typename T, typename Tmin, typename Tmax>
inline static void CheckRange(const char * s, const T& n, int first, int next) inline static void CheckRange(const char * s, const T& n, Tmin first, Tmax next)
{ {
if constexpr (!IsSafe<decltype(n)>()) if constexpr (!IsSafe<decltype(n)>())
if (n<first || n>=next) if (n<first || n>=next)
ThrowRangeException(s, int(n), first, next); ThrowRangeException(s, ptrdiff_t(n), ptrdiff_t(first), ptrdiff_t(next));
} }
template <typename Ta, typename Tb> template <typename Ta, typename Tb>
@ -112,10 +113,10 @@ namespace ngcore
if constexpr (!IsSafe<decltype(a)>() || !IsSafe<decltype(b)>()) if constexpr (!IsSafe<decltype(a)>() || !IsSafe<decltype(b)>())
if(a != b) if(a != b)
{ {
if constexpr(std::is_same<decltype(a),size_t>() && std::is_same<decltype(b),size_t>()) if constexpr(std::is_integral_v<decltype(a)> && std::is_same_v<decltype(a),decltype(b)>)
ThrowNotTheSameException(s, long(a), long(b)); \ ThrowNotTheSameException(s, long(a), long(b)); \
else else
throw Exception(std::string(s) + "\t: not the same"+ToString(a) + ", b="+ngcore::ToString(b) + GetBackTrace()); throw Exception(std::string(s) + "\t: not the same, a="+ToString(a) + ", b="+ngcore::ToString(b) + GetBackTrace());
} }
} }
} // namespace detail } // namespace detail
@ -128,7 +129,7 @@ namespace ngcore
#define NG_EXCEPTION(s) ngcore::Exception(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t"+std::string(s)) #define NG_EXCEPTION(s) ngcore::Exception(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t"+std::string(s))
#if defined(NETGEN_ENABLE_CHECK_RANGE) && !defined(__CUDA_ARCH__) #if defined(NETGEN_ENABLE_CHECK_RANGE) && !defined(__CUDA_ARCH__)
#define NETGEN_CHECK_RANGE(value, min, max_plus_one) ngcore::detail::CheckRange(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t", value, int(min), int(max_plus_one)); #define NETGEN_CHECK_RANGE(value, min, max_plus_one) ngcore::detail::CheckRange(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t", value, min, max_plus_one);
#define NETGEN_CHECK_SAME(a,b) ngcore::detail::CheckSame(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t", a, b); #define NETGEN_CHECK_SAME(a,b) ngcore::detail::CheckSame(__FILE__ ":" NETGEN_CORE_NGEXEPTION_STR(__LINE__) "\t", a, b);
#define NETGEN_NOEXCEPT #define NETGEN_NOEXCEPT

View File

@ -39,7 +39,12 @@ namespace ngcore
NGCORE_API static std::vector<std::string> names; NGCORE_API static std::vector<std::string> names;
NGCORE_API static std::vector<int> parents; NGCORE_API static std::vector<int> parents;
static int CreateId(const std::string& name) #if defined(NETGEN_CHECK_RANGE)
NGCORE_API static std::atomic<size_t> total_memory;
mutable size_t allocated_memory = 0;
#endif // NETGEN_CHECK_RANGE
static int CreateId(const std::string& name = "")
{ {
int id = names.size(); int id = names.size();
names.push_back(name); names.push_back(name);
@ -48,7 +53,7 @@ namespace ngcore
std::cerr << "Allocated " << id << " MemoryTracer objects" << std::endl; std::cerr << "Allocated " << id << " MemoryTracer objects" << std::endl;
return id; return id;
} }
int id; mutable int id = 0;
public: public:
@ -57,8 +62,33 @@ namespace ngcore
id = CreateId(name); id = CreateId(name);
} }
// not tracing MemoryTracer() { }
MemoryTracer() : id(0) {}
MemoryTracer(const MemoryTracer & tracer)
{
(*this) = tracer;
}
MemoryTracer(MemoryTracer && tracer)
{
(*this) = std::move(tracer);
}
MemoryTracer & operator=(const MemoryTracer & tracer) {
if(tracer.id)
id = CreateId(names[tracer.id]);
return *this;
}
MemoryTracer & operator=(MemoryTracer && tracer) {
ngcore::Swap(id, tracer.id);
#if defined(NETGEN_CHECK_RANGE)
ngcore::Swap(allocated_memory, tracer.allocated_memory);
#endif // NETGEN_CHECK_RANGE
return *this;
}
template <typename... TRest> template <typename... TRest>
MemoryTracer( std::string name, TRest & ... rest ) MemoryTracer( std::string name, TRest & ... rest )
@ -67,37 +97,47 @@ namespace ngcore
Track(rest...); Track(rest...);
} }
#if defined(NETGEN_CHECK_RANGE)
// check if all memory was freed when object is destroyed
~MemoryTracer()
{
NETGEN_CHECK_SAME(allocated_memory, 0);
}
#endif // NETGEN_CHECK_RANGE
NETGEN_INLINE void Alloc(size_t size) const NETGEN_INLINE void Alloc(size_t size) const
{ {
#if defined(NETGEN_CHECK_RANGE)
// Trace also nameless Memtracer objects if range checks are active
if(!id && size)
id = CreateId();
#endif // NETGEN_CHECK_RANGE
if(id && trace) if(id && trace)
trace->AllocMemory(id, size); trace->AllocMemory(id, size);
#if defined(NETGEN_CHECK_RANGE)
if(id)
{
allocated_memory += size;
total_memory += size;
}
#endif // NETGEN_CHECK_RANGE
} }
void Free(size_t size) const void Free(size_t size) const
{ {
if(id && trace) if(id && trace)
trace->FreeMemory(id, size); trace->FreeMemory(id, size);
}
void Swap(size_t mysize, MemoryTracer& other, size_t other_size) const #if defined(NETGEN_CHECK_RANGE)
if(id)
{ {
if(!trace || (id == 0 && other.id == 0)) // check if we have at least size bytes of memory currently allocated (such that allocated_memory doesn't get negative)
return; NETGEN_CHECK_RANGE(allocated_memory, static_cast<ptrdiff_t>(size), std::numeric_limits<ptrdiff_t>::max());
if(id == 0) allocated_memory -= size;
return trace->ChangeMemory(other.id, mysize - other_size); total_memory -= size;
if(other.id == 0) #endif // NETGEN_CHECK_RANGE
return trace->ChangeMemory(id, other_size - mysize);
// first decrease memory, otherwise have artificial/wrong high peak memory usage
if(mysize<other_size)
{
trace->ChangeMemory(other.id, mysize-other_size);
trace->ChangeMemory(id, other_size-mysize);
}
else
{
trace->ChangeMemory(id, other_size-mysize);
trace->ChangeMemory(other.id, mysize-other_size);
} }
} }
@ -148,6 +188,14 @@ namespace ngcore
static const std::vector<std::string> & GetNames() { return names; } static const std::vector<std::string> & GetNames() { return names; }
static const std::vector<int> & GetParents() { return parents; } static const std::vector<int> & GetParents() { return parents; }
static size_t GetTotalMemory()
{
#if defined(NETGEN_CHECK_RANGE)
return total_memory;
#else
return 0;
#endif // NETGEN_CHECK_RANGE
}
#else // defined(NETGEN_TRACE_MEMORY) && !defined(__CUDA_ARCH__) #else // defined(NETGEN_TRACE_MEMORY) && !defined(__CUDA_ARCH__)
public: public:
MemoryTracer() {} MemoryTracer() {}
@ -157,7 +205,6 @@ namespace ngcore
void Alloc(size_t /* size */) const {} void Alloc(size_t /* size */) const {}
void Free(size_t /* size */) const {} void Free(size_t /* size */) const {}
void Swap(...) const {}
int GetId() const { return 0; } int GetId() const { return 0; }
template <typename... TRest> template <typename... TRest>
@ -166,6 +213,7 @@ namespace ngcore
static std::string GetName(int /* id */) { return ""; } static std::string GetName(int /* id */) { return ""; }
std::string GetName() const { return ""; } std::string GetName() const { return ""; }
void SetName(std::string /* name */) const {} void SetName(std::string /* name */) const {}
static size_t GetTotalMemory() { return 0; }
#endif // NETGEN_TRACE_MEMORY #endif // NETGEN_TRACE_MEMORY
}; };
} // namespace ngcore } // namespace ngcore

View File

@ -116,6 +116,7 @@ namespace ngcore
#ifdef NETGEN_TRACE_MEMORY #ifdef NETGEN_TRACE_MEMORY
std::vector<std::string> MemoryTracer::names{"all"}; std::vector<std::string> MemoryTracer::names{"all"};
std::vector<int> MemoryTracer::parents{-1}; std::vector<int> MemoryTracer::parents{-1};
std::atomic<size_t> MemoryTracer::total_memory{0};
#endif // NETGEN_TRACE_MEMORY #endif // NETGEN_TRACE_MEMORY
} // namespace ngcore } // namespace ngcore

View File

@ -324,6 +324,8 @@ threads : int
#endif // NETGEN_TRACE_MEMORY #endif // NETGEN_TRACE_MEMORY
; ;
m.def("GetTotalMemory", MemoryTracer::GetTotalMemory);
py::class_<Timer<>> (m, "Timer") py::class_<Timer<>> (m, "Timer")
.def(py::init<const string&>()) .def(py::init<const string&>())
.def("Start", static_cast<void (Timer<>::*)()const>(&Timer<>::Start), "start timer") .def("Start", static_cast<void (Timer<>::*)()const>(&Timer<>::Start), "start timer")

View File

@ -130,6 +130,7 @@ namespace ngcore
{ {
for (size_t i : IntRange(size+1)) for (size_t i : IntRange(size+1))
index[i] = i*entrysize; index[i] = i*entrysize;
mt.Alloc(GetMemUsage());
} }
/// Construct table of variable entrysize /// Construct table of variable entrysize
@ -141,6 +142,7 @@ namespace ngcore
index = TablePrefixSum (FlatArray<TI> (entrysize.Size(), entrysize.Data())); index = TablePrefixSum (FlatArray<TI> (entrysize.Size(), entrysize.Data()));
size_t cnt = index[size]; size_t cnt = index[size];
data = new T[cnt]; data = new T[cnt];
mt.Alloc(GetMemUsage());
} }
explicit NETGEN_INLINE Table (const FlatTable<T,IndexType> & tab2) explicit NETGEN_INLINE Table (const FlatTable<T,IndexType> & tab2)
@ -157,6 +159,7 @@ namespace ngcore
size_t cnt = index[size]; size_t cnt = index[size];
data = new T[cnt]; data = new T[cnt];
this->AsArray() = tab2.AsArray(); this->AsArray() = tab2.AsArray();
mt.Alloc(GetMemUsage());
/* /*
for (size_t i = 0; i < cnt; i++) for (size_t i = 0; i < cnt; i++)
data[i] = tab2.data[i]; data[i] = tab2.data[i];
@ -177,12 +180,14 @@ namespace ngcore
data = new T[cnt]; data = new T[cnt];
for (size_t i = 0; i < cnt; i++) for (size_t i = 0; i < cnt; i++)
data[i] = tab2.data[i]; data[i] = tab2.data[i];
mt.Alloc(GetMemUsage());
} }
NETGEN_INLINE Table (Table && tab2) NETGEN_INLINE Table (Table && tab2)
: FlatTable<T,IndexType>(0, nullptr, nullptr) : FlatTable<T,IndexType>(0, nullptr, nullptr)
{ {
tab2.mt.Free(tab2.GetMemUsage()); mt = std::move(tab2.mt);
Swap (size, tab2.size); Swap (size, tab2.size);
Swap (index, tab2.index); Swap (index, tab2.index);
Swap (data, tab2.data); Swap (data, tab2.data);
@ -210,7 +215,7 @@ namespace ngcore
NETGEN_INLINE Table & operator= (Table && tab2) NETGEN_INLINE Table & operator= (Table && tab2)
{ {
mt.Swap(GetMemUsage(), tab2.mt, tab2.GetMemUsage()); mt = std::move(tab2.mt);
Swap (size, tab2.size); Swap (size, tab2.size);
Swap (index, tab2.index); Swap (index, tab2.index);
Swap (data, tab2.data); Swap (data, tab2.data);