|
| 1 | +#ifndef GK_MEMORY_POOL_H |
| 2 | +#define GK_MEMORY_POOL_H |
| 3 | + |
| 4 | +#include <algorithm> |
| 5 | +#include <cstddef> |
| 6 | +#include <cstdint> |
| 7 | +#include <new> |
| 8 | + |
| 9 | +namespace gk { |
| 10 | + |
| 11 | +struct MemoryStats { |
| 12 | + size_t peak_usage = 0; |
| 13 | + size_t current_usage = 0; |
| 14 | + size_t total_allocations = 0; |
| 15 | + size_t total_frees = 0; |
| 16 | + size_t failed_allocations = 0; |
| 17 | + size_t hot_available = 0; |
| 18 | + size_t work_used = 0; |
| 19 | + size_t work_capacity = 0; |
| 20 | + size_t work_peak = 0; |
| 21 | +}; |
| 22 | + |
| 23 | +class MemoryTracker { |
| 24 | +public: |
| 25 | + void trackExternalAlloc(size_t size) { |
| 26 | + if (size == 0) return; |
| 27 | + stats_.current_usage += size; |
| 28 | + stats_.peak_usage = std::max(stats_.peak_usage, stats_.current_usage); |
| 29 | + ++stats_.total_allocations; |
| 30 | + } |
| 31 | + |
| 32 | + void trackExternalFree(size_t size) { |
| 33 | + if (size == 0) return; |
| 34 | + stats_.current_usage = stats_.current_usage > size ? stats_.current_usage - size : 0; |
| 35 | + ++stats_.total_frees; |
| 36 | + } |
| 37 | + |
| 38 | + void trackFailedAllocation() { |
| 39 | + ++stats_.failed_allocations; |
| 40 | + } |
| 41 | + |
| 42 | + void trackWorkReserve(size_t size) { |
| 43 | + stats_.work_capacity = size; |
| 44 | + trackExternalAlloc(size); |
| 45 | + } |
| 46 | + |
| 47 | + void trackWorkRelease(size_t reservedSize, size_t usedSize) { |
| 48 | + stats_.work_used = stats_.work_used > usedSize ? stats_.work_used - usedSize : 0; |
| 49 | + stats_.work_capacity = stats_.work_capacity > reservedSize ? stats_.work_capacity - reservedSize : 0; |
| 50 | + trackExternalFree(reservedSize); |
| 51 | + } |
| 52 | + |
| 53 | + void trackWorkUsage(size_t usedSize) { |
| 54 | + stats_.work_used = usedSize; |
| 55 | + stats_.work_peak = std::max(stats_.work_peak, usedSize); |
| 56 | + ++stats_.total_allocations; |
| 57 | + } |
| 58 | + |
| 59 | + void resetWorkUsage() { |
| 60 | + stats_.work_used = 0; |
| 61 | + } |
| 62 | + |
| 63 | + void setHotAvailable(size_t available) { |
| 64 | + stats_.hot_available = available; |
| 65 | + } |
| 66 | + |
| 67 | + void resetCountersPreserveLiveAllocations() { |
| 68 | + stats_.peak_usage = stats_.current_usage; |
| 69 | + stats_.total_allocations = 0; |
| 70 | + stats_.total_frees = 0; |
| 71 | + stats_.failed_allocations = 0; |
| 72 | + stats_.work_peak = std::max(stats_.work_peak, stats_.work_used); |
| 73 | + } |
| 74 | + |
| 75 | + MemoryStats snapshot() const { |
| 76 | + return stats_; |
| 77 | + } |
| 78 | + |
| 79 | +private: |
| 80 | + MemoryStats stats_{}; |
| 81 | +}; |
| 82 | + |
| 83 | +inline MemoryTracker& memoryTracker() { |
| 84 | + static MemoryTracker tracker; |
| 85 | + return tracker; |
| 86 | +} |
| 87 | + |
| 88 | +inline void trackExternalAlloc(size_t size) { |
| 89 | + memoryTracker().trackExternalAlloc(size); |
| 90 | +} |
| 91 | + |
| 92 | +inline void trackExternalFree(size_t size) { |
| 93 | + memoryTracker().trackExternalFree(size); |
| 94 | +} |
| 95 | + |
| 96 | +inline void trackFailedAllocation() { |
| 97 | + memoryTracker().trackFailedAllocation(); |
| 98 | +} |
| 99 | + |
| 100 | +inline void resetMemoryStats() { |
| 101 | + memoryTracker().resetCountersPreserveLiveAllocations(); |
| 102 | +} |
| 103 | + |
| 104 | +class BumpAllocator { |
| 105 | +public: |
| 106 | + BumpAllocator() = default; |
| 107 | + |
| 108 | + explicit BumpAllocator(size_t size) { |
| 109 | + init(size); |
| 110 | + } |
| 111 | + |
| 112 | + BumpAllocator(const BumpAllocator&) = delete; |
| 113 | + BumpAllocator& operator=(const BumpAllocator&) = delete; |
| 114 | + |
| 115 | + ~BumpAllocator() { |
| 116 | + release(); |
| 117 | + } |
| 118 | + |
| 119 | + bool init(size_t size) { |
| 120 | + release(); |
| 121 | + if (size == 0) return true; |
| 122 | + |
| 123 | + pool_ = new (std::nothrow) std::byte[size]; |
| 124 | + if (pool_ == nullptr) { |
| 125 | + trackFailedAllocation(); |
| 126 | + return false; |
| 127 | + } |
| 128 | + |
| 129 | + capacity_ = size; |
| 130 | + current_offset_ = 0; |
| 131 | + peak_usage_ = 0; |
| 132 | + allocations_ = 0; |
| 133 | + memoryTracker().trackWorkReserve(size); |
| 134 | + return true; |
| 135 | + } |
| 136 | + |
| 137 | + void release() { |
| 138 | + if (pool_ == nullptr) return; |
| 139 | + memoryTracker().trackWorkRelease(capacity_, current_offset_); |
| 140 | + delete[] pool_; |
| 141 | + pool_ = nullptr; |
| 142 | + capacity_ = 0; |
| 143 | + current_offset_ = 0; |
| 144 | + peak_usage_ = 0; |
| 145 | + allocations_ = 0; |
| 146 | + } |
| 147 | + |
| 148 | + void* alloc(size_t size, size_t alignment = alignof(std::max_align_t)) { |
| 149 | + if (pool_ == nullptr || size == 0) return nullptr; |
| 150 | + |
| 151 | + const size_t aligned_offset = alignUp(current_offset_, alignment); |
| 152 | + if (aligned_offset > capacity_ || capacity_ - aligned_offset < size) { |
| 153 | + trackFailedAllocation(); |
| 154 | + return nullptr; |
| 155 | + } |
| 156 | + |
| 157 | + void* ptr = pool_ + aligned_offset; |
| 158 | + current_offset_ = aligned_offset + size; |
| 159 | + peak_usage_ = std::max(peak_usage_, current_offset_); |
| 160 | + ++allocations_; |
| 161 | + memoryTracker().trackWorkUsage(current_offset_); |
| 162 | + return ptr; |
| 163 | + } |
| 164 | + |
| 165 | + void reset() { |
| 166 | + current_offset_ = 0; |
| 167 | + memoryTracker().resetWorkUsage(); |
| 168 | + } |
| 169 | + |
| 170 | + size_t used() const { return current_offset_; } |
| 171 | + size_t peak_usage() const { return peak_usage_; } |
| 172 | + size_t remaining() const { return capacity_ > current_offset_ ? capacity_ - current_offset_ : 0; } |
| 173 | + size_t allocations() const { return allocations_; } |
| 174 | + size_t capacity() const { return capacity_; } |
| 175 | + |
| 176 | +private: |
| 177 | + static size_t alignUp(size_t value, size_t alignment) { |
| 178 | + const size_t mask = alignment - 1; |
| 179 | + return (value + mask) & ~mask; |
| 180 | + } |
| 181 | + |
| 182 | + std::byte* pool_ = nullptr; |
| 183 | + size_t capacity_ = 0; |
| 184 | + size_t current_offset_ = 0; |
| 185 | + size_t peak_usage_ = 0; |
| 186 | + size_t allocations_ = 0; |
| 187 | +}; |
| 188 | + |
| 189 | +template<size_t ObjectSize, uint32_t PoolSize> |
| 190 | +class HotObjectPool { |
| 191 | + static_assert(PoolSize > 0, "PoolSize must be > 0"); |
| 192 | + static_assert(ObjectSize >= sizeof(void*), "ObjectSize must be >= sizeof(void*)"); |
| 193 | + |
| 194 | + struct FreeNode { |
| 195 | + FreeNode* next; |
| 196 | + }; |
| 197 | + |
| 198 | +public: |
| 199 | + HotObjectPool() : freeList_(nullptr), allocations_(0) { |
| 200 | + for (uint32_t i = 0; i < PoolSize; ++i) { |
| 201 | + auto* node = reinterpret_cast<FreeNode*>(pool_ + i * ObjectSize); |
| 202 | + node->next = freeList_; |
| 203 | + freeList_ = node; |
| 204 | + } |
| 205 | + memoryTracker().setHotAvailable(PoolSize); |
| 206 | + } |
| 207 | + |
| 208 | + void* alloc() { |
| 209 | + if (freeList_ == nullptr) return nullptr; |
| 210 | + auto* node = freeList_; |
| 211 | + freeList_ = freeList_->next; |
| 212 | + ++allocations_; |
| 213 | + memoryTracker().setHotAvailable(available()); |
| 214 | + return node; |
| 215 | + } |
| 216 | + |
| 217 | + void dealloc(void* ptr) { |
| 218 | + auto* node = reinterpret_cast<FreeNode*>(ptr); |
| 219 | + node->next = freeList_; |
| 220 | + freeList_ = node; |
| 221 | + memoryTracker().setHotAvailable(available()); |
| 222 | + } |
| 223 | + |
| 224 | + uint32_t available() const { |
| 225 | + uint32_t count = 0; |
| 226 | + for (auto* node = freeList_; node != nullptr; node = node->next) { |
| 227 | + ++count; |
| 228 | + } |
| 229 | + return count; |
| 230 | + } |
| 231 | + |
| 232 | + uint32_t allocations() const { return allocations_; } |
| 233 | + |
| 234 | +private: |
| 235 | + FreeNode* freeList_; |
| 236 | + alignas(std::max_align_t) std::byte pool_[ObjectSize * PoolSize]; |
| 237 | + uint32_t allocations_; |
| 238 | +}; |
| 239 | + |
| 240 | +inline MemoryStats getMemoryStats() { |
| 241 | + return memoryTracker().snapshot(); |
| 242 | +} |
| 243 | + |
| 244 | +} // namespace gk |
| 245 | + |
| 246 | +#endif // GK_MEMORY_POOL_H |
0 commit comments