Line data Source code
1 : #ifndef HASHBROWNS_MEMORY_MANAGER_H
2 : #define HASHBROWNS_MEMORY_MANAGER_H
3 :
4 : #include <cstddef>
5 : #include <memory>
6 : #include <atomic>
7 : #include <unordered_map>
8 : #include <mutex>
9 : #include <string>
10 : #include <vector>
11 : #include <type_traits>
12 :
13 : namespace hashbrowns {
14 :
15 : /**
16 : * @brief Memory tracking and debugging utilities for the benchmarking suite
17 : *
18 : * This class provides detailed memory allocation tracking, leak detection,
19 : * and performance analysis for custom data structures. It's designed to be
20 : * lightweight in production but comprehensive for development and testing.
21 : */
22 : class MemoryTracker {
23 : public:
24 : /**
25 : * @brief Statistics about memory allocation patterns
26 : */
27 : struct Stats {
28 : size_t total_allocated{0}; ///< Total bytes allocated
29 : size_t total_deallocated{0}; ///< Total bytes deallocated
30 : size_t current_usage{0}; ///< Current memory usage
31 : size_t peak_usage{0}; ///< Peak memory usage
32 : size_t allocation_count{0}; ///< Number of allocations
33 : size_t deallocation_count{0}; ///< Number of deallocations
34 :
35 : /**
36 : * @brief Get current memory leakage (allocated - deallocated)
37 : */
38 5 : size_t memory_leaked() const {
39 5 : return total_allocated - total_deallocated;
40 : }
41 :
42 : /**
43 : * @brief Get number of outstanding allocations
44 : */
45 2 : size_t outstanding_allocations() const {
46 2 : return allocation_count - deallocation_count;
47 : }
48 : };
49 :
50 : /**
51 : * @brief Get the singleton instance of the memory tracker
52 : */
53 : static MemoryTracker& instance();
54 :
55 : /**
56 : * @brief Record a memory allocation
57 : * @param ptr Pointer to allocated memory
58 : * @param size Size of allocation in bytes
59 : * @param file Source file where allocation occurred
60 : * @param line Line number where allocation occurred
61 : */
62 : void record_allocation(void* ptr, size_t size, const char* file = nullptr, int line = 0);
63 :
64 : /**
65 : * @brief Record a memory deallocation
66 : * @param ptr Pointer to memory being deallocated
67 : */
68 : void record_deallocation(void* ptr);
69 :
70 : /**
71 : * @brief Get current memory statistics
72 : */
73 125 : Stats get_stats() const {
74 125 : std::lock_guard<std::mutex> lock(mutex_);
75 250 : return stats_;
76 125 : }
77 :
78 : /**
79 : * @brief Reset all tracking statistics
80 : */
81 : void reset();
82 :
83 : /**
84 : * @brief Check for memory leaks and print report
85 : * @return true if no leaks detected, false otherwise
86 : */
87 : bool check_leaks() const;
88 :
89 : /**
90 : * @brief Enable or disable detailed tracking (affects performance)
91 : */
92 10 : void set_detailed_tracking(bool enabled) { detailed_tracking_ = enabled; }
93 :
94 : private:
95 1 : MemoryTracker() = default;
96 1 : ~MemoryTracker() = default;
97 : MemoryTracker(const MemoryTracker&) = delete;
98 : MemoryTracker& operator=(const MemoryTracker&) = delete;
99 :
100 : mutable std::mutex mutex_;
101 : Stats stats_;
102 : bool detailed_tracking_ = false;
103 : std::unordered_map<void*, size_t> allocations_; ///< ptr -> size mapping for leak detection
104 : };
105 :
106 : /**
107 : * @brief Custom allocator that integrates with memory tracking
108 : *
109 : * This allocator can be used with STL containers or custom data structures
110 : * to provide detailed allocation tracking and analysis.
111 : */
112 : template<typename T>
113 : class TrackedAllocator {
114 : public:
115 : using value_type = T;
116 : using size_type = std::size_t;
117 : using difference_type = std::ptrdiff_t;
118 :
119 : TrackedAllocator() = default;
120 :
121 : template<typename U>
122 : TrackedAllocator(const TrackedAllocator<U>&) noexcept {}
123 :
124 : /**
125 : * @brief Allocate memory for n objects of type T
126 : */
127 246 : T* allocate(size_type n) {
128 246 : if (n > std::numeric_limits<size_type>::max() / sizeof(T)) {
129 0 : throw std::bad_alloc();
130 : }
131 :
132 246 : size_t bytes = n * sizeof(T);
133 246 : T* ptr = static_cast<T*>(std::malloc(bytes));
134 :
135 246 : if (!ptr) {
136 0 : throw std::bad_alloc();
137 : }
138 :
139 246 : MemoryTracker::instance().record_allocation(ptr, bytes);
140 246 : return ptr;
141 : }
142 :
143 : /**
144 : * @brief Deallocate memory previously allocated by this allocator
145 : */
146 246 : void deallocate(T* ptr, size_type) noexcept {
147 246 : if (ptr) {
148 246 : MemoryTracker::instance().record_deallocation(ptr);
149 246 : std::free(ptr);
150 : }
151 246 : }
152 :
153 : template<typename U>
154 : bool operator==(const TrackedAllocator<U>&) const noexcept { return true; }
155 :
156 : template<typename U>
157 : bool operator!=(const TrackedAllocator<U>&) const noexcept { return false; }
158 : };
159 :
160 : /**
161 : * @brief RAII wrapper for raw memory management
162 : *
163 : * This class provides safe, automatic cleanup of raw memory allocations
164 : * while integrating with the memory tracking system.
165 : */
166 : template<typename T>
167 : class unique_array {
168 : public:
169 : /**
170 : * @brief Construct from raw pointer (takes ownership)
171 : */
172 62 : explicit unique_array(T* ptr = nullptr, size_t size = 0)
173 62 : : ptr_(ptr), size_(size) {
174 62 : if (ptr_ && size_ > 0) {
175 62 : MemoryTracker::instance().record_allocation(ptr_, size_ * sizeof(T));
176 : }
177 62 : }
178 :
179 : /**
180 : * @brief Destructor - automatically deallocates memory
181 : */
182 123 : ~unique_array() {
183 123 : reset();
184 123 : }
185 :
186 : // Move semantics
187 61 : unique_array(unique_array&& other) noexcept
188 61 : : ptr_(other.ptr_), size_(other.size_) {
189 61 : other.ptr_ = nullptr;
190 61 : other.size_ = 0;
191 61 : }
192 :
193 : unique_array& operator=(unique_array&& other) noexcept {
194 : if (this != &other) {
195 : reset();
196 : ptr_ = other.ptr_;
197 : size_ = other.size_;
198 : other.ptr_ = nullptr;
199 : other.size_ = 0;
200 : }
201 : return *this;
202 : }
203 :
204 : // Disable copying
205 : unique_array(const unique_array&) = delete;
206 : unique_array& operator=(const unique_array&) = delete;
207 :
208 : /**
209 : * @brief Access element at index
210 : */
211 62464 : T& operator[](size_t index) {
212 62464 : return ptr_[index];
213 : }
214 :
215 : const T& operator[](size_t index) const {
216 : return ptr_[index];
217 : }
218 :
219 : /**
220 : * @brief Get raw pointer
221 : */
222 : T* get() const noexcept { return ptr_; }
223 :
224 : /**
225 : * @brief Get array size
226 : */
227 : size_t size() const noexcept { return size_; }
228 :
229 : /**
230 : * @brief Release ownership and return raw pointer
231 : */
232 : T* release() noexcept {
233 : T* result = ptr_;
234 : ptr_ = nullptr;
235 : size_ = 0;
236 : return result;
237 : }
238 :
239 : /**
240 : * @brief Reset to new pointer (deallocates current)
241 : */
242 123 : void reset(T* ptr = nullptr, size_t size = 0) {
243 123 : if (ptr_ && size_ > 0) {
244 62 : MemoryTracker::instance().record_deallocation(ptr_);
245 62 : std::free(ptr_);
246 : }
247 123 : ptr_ = ptr;
248 123 : size_ = size;
249 123 : if (ptr_ && size_ > 0) {
250 0 : MemoryTracker::instance().record_allocation(ptr_, size_ * sizeof(T));
251 : }
252 123 : }
253 :
254 : /**
255 : * @brief Check if pointer is valid
256 : */
257 : explicit operator bool() const noexcept {
258 : return ptr_ != nullptr;
259 : }
260 :
261 : private:
262 : T* ptr_;
263 : size_t size_;
264 : };
265 :
266 : /**
267 : * @brief Factory function to create tracked arrays
268 : */
269 : template<typename T>
270 62 : unique_array<T> make_unique_array(size_t count) {
271 62 : if (count == 0) {
272 0 : return unique_array<T>(nullptr, 0);
273 : }
274 :
275 62 : T* ptr = static_cast<T*>(std::malloc(count * sizeof(T)));
276 62 : if (!ptr) {
277 0 : throw std::bad_alloc();
278 : }
279 :
280 62 : return unique_array<T>(ptr, count);
281 : }
282 :
283 : /**
284 : * @brief Memory pool for efficient allocation of fixed-size objects
285 : *
286 : * This pool reduces allocation overhead and fragmentation for data structures
287 : * that frequently allocate/deallocate objects of the same size (e.g., list nodes).
288 : */
289 : template<typename T, size_t ChunkSize = 1024>
290 : class MemoryPool {
291 : public:
292 61 : MemoryPool() : free_list_(nullptr) {
293 61 : add_chunk();
294 61 : }
295 :
296 : /**
297 : * @brief Allocate one object from the pool (grows in chunks on demand)
298 : */
299 638 : T* allocate() {
300 638 : std::lock_guard<std::mutex> lock(mutex_);
301 638 : if (!free_list_) {
302 0 : add_chunk();
303 : }
304 : // Pop from free list
305 638 : Slot* slot = free_list_;
306 638 : free_list_ = free_list_->next;
307 638 : return reinterpret_cast<T*>(slot);
308 638 : }
309 :
310 : /**
311 : * @brief Return an object to the pool
312 : */
313 638 : void deallocate(T* ptr) {
314 638 : if (!ptr) return;
315 638 : std::lock_guard<std::mutex> lock(mutex_);
316 638 : Slot* slot = reinterpret_cast<Slot*>(ptr);
317 638 : slot->next = free_list_;
318 638 : free_list_ = slot;
319 638 : }
320 :
321 : private:
322 : // Internal slot used for free list linking; shares storage with raw storage for T
323 : struct Slot { Slot* next; };
324 : using Storage = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
325 :
326 61 : void add_chunk() {
327 61 : unique_array<Storage> chunk = make_unique_array<Storage>(ChunkSize);
328 : // Link all new slots into the free list
329 : // We push in forward order; order doesn't matter
330 62525 : for (size_t i = 0; i < ChunkSize; ++i) {
331 62464 : Storage* cell = &chunk[i];
332 62464 : Slot* slot = reinterpret_cast<Slot*>(cell);
333 62464 : slot->next = free_list_;
334 62464 : free_list_ = slot;
335 : }
336 61 : chunks_.push_back(std::move(chunk));
337 122 : }
338 :
339 : std::vector< unique_array<Storage> > chunks_;
340 : Slot* free_list_;
341 : std::mutex mutex_;
342 : };
343 :
344 : } // namespace hashbrowns
345 :
346 : // Convenience macros for tracked allocation (optional, for debugging)
347 : #ifdef HASHBROWNS_TRACK_ALLOCATIONS
348 : #define TRACKED_NEW(size) hashbrowns::MemoryTracker::instance().record_allocation(std::malloc(size), size, __FILE__, __LINE__)
349 : #define TRACKED_DELETE(ptr) do { hashbrowns::MemoryTracker::instance().record_deallocation(ptr); std::free(ptr); } while(0)
350 : #else
351 : #define TRACKED_NEW(size) std::malloc(size)
352 : #define TRACKED_DELETE(ptr) std::free(ptr)
353 : #endif
354 :
355 : #endif // HASHBROWNS_MEMORY_MANAGER_H
|