unilink  0.4.3
A simple C++ library for unified async communication
memory_pool.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2025 Jinwoo Sung
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
18 
19 #include <algorithm>
20 #include <cstdlib>
21 #include <stdexcept>
22 
24 
25 namespace unilink {
26 namespace memory {
27 
28 // ============================================================================
29 // SelectiveMemoryPool Implementation
30 // ============================================================================
31 
32 MemoryPool::MemoryPool(size_t initial_pool_size, size_t max_pool_size) {
33  // Suppress unused parameter warning since we reserve based on max_pool_size
34  (void)initial_pool_size;
35 
36  // Initialize 4 fixed-size pools
37  static constexpr std::array<size_t, 4> BUCKET_SIZES = {
38  static_cast<size_t>(BufferSize::SMALL), // 1KB
39  static_cast<size_t>(BufferSize::MEDIUM), // 4KB
40  static_cast<size_t>(BufferSize::LARGE), // 16KB
41  static_cast<size_t>(BufferSize::XLARGE) // 64KB
42  };
43 
44  for (size_t i = 0; i < buckets_.size(); ++i) {
45  buckets_[i].size_ = BUCKET_SIZES[i];
46  buckets_[i].capacity_ = max_pool_size / buckets_.size();
47  buckets_[i].buffers_.reserve(buckets_[i].capacity_);
48  }
49 }
50 
51 std::unique_ptr<uint8_t[]> MemoryPool::acquire(size_t size) {
52  validate_size(size);
53 
54  // Optimization: Bypass pool for large allocations > 64KB (XLARGE)
55  // This avoids mutex contention and fixes truncation bug for sizes > 64KB.
56  if (size > static_cast<size_t>(BufferSize::XLARGE)) {
57  auto buffer = create_buffer(size);
58  // create_buffer throws on failure, so if we are here, we succeeded.
59  total_allocations_.fetch_add(1, std::memory_order_relaxed);
60  return buffer;
61  }
62 
63  auto& bucket = get_bucket(size);
64  return acquire_from_bucket(bucket);
65 }
66 
67 std::unique_ptr<uint8_t[]> MemoryPool::acquire(BufferSize buffer_size) {
68  return acquire(static_cast<size_t>(buffer_size));
69 }
70 
71 void MemoryPool::release(std::unique_ptr<uint8_t[]> buffer, size_t size) {
72  if (!buffer) return;
73 
74  validate_size(size);
75 
76  if (size > static_cast<size_t>(BufferSize::XLARGE)) {
77  MEMORY_TRACK_DEALLOCATION(buffer.get());
78  return; // unique_ptr destructor handles cleanup
79  }
80 
81  auto& bucket = get_bucket(size);
82  release_to_bucket(bucket, std::move(buffer));
83 }
84 
86  PoolStats stats;
87  stats.total_allocations = total_allocations_.load(std::memory_order_relaxed);
88  stats.pool_hits = pool_hits_.load(std::memory_order_relaxed);
89  return stats;
90 }
91 
92 double MemoryPool::get_hit_rate() const {
93  size_t total = total_allocations_.load(std::memory_order_relaxed);
94  if (total == 0) return 0.0;
95 
96  size_t hits = pool_hits_.load(std::memory_order_relaxed);
97  return static_cast<double>(hits) / static_cast<double>(total);
98 }
99 
100 void MemoryPool::cleanup_old_buffers(std::chrono::milliseconds max_age) {
101  // Simplified: cleanup functionality disabled
102  (void)max_age; // prevent unused parameter warning
103 }
104 
105 std::pair<size_t, size_t> MemoryPool::get_memory_usage() const {
106  // Simplified: return basic memory usage
107  size_t total_allocs = total_allocations_.load(std::memory_order_relaxed);
108  size_t current_usage = total_allocs * 4096; // estimate average buffer size
109  return std::make_pair(current_usage, current_usage);
110 }
111 
112 void MemoryPool::resize_pool(size_t new_size) {
113  // Simplified: resize functionality disabled
114  (void)new_size; // prevent unused parameter warning
115 }
116 
118  // Simplified: auto_tune functionality disabled
119 }
120 
122  HealthMetrics metrics;
123  metrics.hit_rate = get_hit_rate();
124  return metrics;
125 }
126 
127 // ============================================================================
128 // Private helper functions
129 // ============================================================================
130 
131 MemoryPool::PoolBucket& MemoryPool::get_bucket(size_t size) { return buckets_[get_bucket_index(size)]; }
132 
133 size_t MemoryPool::get_bucket_index(size_t size) const {
134  // Optimized: Unrolled checks for faster lookup
135  if (size <= static_cast<size_t>(BufferSize::SMALL)) return 0;
136  if (size <= static_cast<size_t>(BufferSize::MEDIUM)) return 1;
137  if (size <= static_cast<size_t>(BufferSize::LARGE)) return 2;
138  return 3; // XLARGE
139 }
140 
141 std::unique_ptr<uint8_t[]> MemoryPool::acquire_from_bucket(PoolBucket& bucket) {
142  std::unique_ptr<uint8_t[]> buffer;
143 
144  {
145  std::lock_guard<std::mutex> lock(bucket.mutex_);
146 
147  // Get from stack
148  if (!bucket.buffers_.empty()) {
149  buffer = std::move(bucket.buffers_.back());
150  bucket.buffers_.pop_back();
151  }
152  }
153 
154  if (buffer) {
155  pool_hits_.fetch_add(1, std::memory_order_relaxed);
156  total_allocations_.fetch_add(1, std::memory_order_relaxed);
157  return buffer;
158  }
159 
160  // Create new buffer outside lock
161  buffer = create_buffer(bucket.size_);
162  if (buffer) {
163  total_allocations_.fetch_add(1, std::memory_order_relaxed);
164  }
165 
166  return buffer;
167 }
168 
169 void MemoryPool::release_to_bucket(PoolBucket& bucket, std::unique_ptr<uint8_t[]> buffer) {
170  {
171  std::lock_guard<std::mutex> lock(bucket.mutex_);
172 
173  // Add buffer back to pool (stack)
174  if (bucket.buffers_.size() < bucket.capacity_) {
175  bucket.buffers_.push_back(std::move(buffer));
176  return;
177  }
178  }
179 
180  // If pool is full, discard buffer (auto-release)
181  // buffer is unique_ptr so it will be automatically deleted when out of scope
182  MEMORY_TRACK_DEALLOCATION(buffer.get());
183 }
184 
185 std::unique_ptr<uint8_t[]> MemoryPool::create_buffer(size_t size) {
186  uint8_t* raw_buffer = new (std::nothrow) uint8_t[size];
187  if (raw_buffer) {
188  MEMORY_TRACK_ALLOCATION(raw_buffer, size);
189  } else {
190  throw std::bad_alloc(); // Or handle error appropriately
191  }
192  return std::unique_ptr<uint8_t[]>(raw_buffer);
193 }
194 
195 void MemoryPool::validate_size(size_t size) const {
196  if (size == 0 || size > 64 * 1024 * 1024) { // 64MB maximum
197  throw std::invalid_argument("Invalid buffer size");
198  }
199 }
200 
201 // ============================================================================
202 // PoolBucket Move Constructor/Assignment Operator
203 // ============================================================================
204 
205 MemoryPool::PoolBucket::PoolBucket(PoolBucket&& other) noexcept
206  : buffers_(std::move(other.buffers_)), mutex_(), size_(other.size_), capacity_(other.capacity_) {
207  other.size_ = 0;
208  other.capacity_ = 0;
209 }
210 
211 MemoryPool::PoolBucket& MemoryPool::PoolBucket::operator=(PoolBucket&& other) noexcept {
212  if (this != &other) {
213  buffers_ = std::move(other.buffers_);
214  size_ = other.size_;
215  capacity_ = other.capacity_;
216 
217  other.size_ = 0;
218  other.capacity_ = 0;
219  }
220  return *this;
221 }
222 
223 // ============================================================================
224 // PooledBuffer Implementation
225 // ============================================================================
226 
227 PooledBuffer::PooledBuffer(size_t size) : size_(size), pool_(&GlobalMemoryPool::instance()) {
228  buffer_ = pool_->acquire(size);
229 }
230 
232  : size_(static_cast<size_t>(buffer_size)), pool_(&GlobalMemoryPool::instance()) {
233  buffer_ = pool_->acquire(size_);
234 }
235 
237  if (buffer_ && pool_) {
238  pool_->release(std::move(buffer_), size_);
239  }
240 }
241 
243  : buffer_(std::move(other.buffer_)), size_(other.size_), pool_(other.pool_) {
244  other.buffer_ = nullptr;
245  other.size_ = 0;
246  other.pool_ = nullptr;
247 }
248 
250  if (this != &other) {
251  if (buffer_ && pool_) {
252  try {
253  pool_->release(std::move(buffer_), size_);
254  } catch (...) {
255  // Ignore exception and continue (noexcept function)
256  }
257  }
258 
259  buffer_ = std::move(other.buffer_);
260  size_ = other.size_;
261  pool_ = other.pool_;
262 
263  other.buffer_ = nullptr;
264  other.size_ = 0;
265  other.pool_ = nullptr;
266  }
267  return *this;
268 }
269 
270 uint8_t* PooledBuffer::data() const { return buffer_.get(); }
271 
272 size_t PooledBuffer::size() const { return size_; }
273 
274 bool PooledBuffer::valid() const { return buffer_ != nullptr; }
275 
276 uint8_t& PooledBuffer::operator[](size_t index) { return buffer_[index]; }
277 
278 const uint8_t& PooledBuffer::operator[](size_t index) const { return buffer_[index]; }
279 
280 uint8_t& PooledBuffer::at(size_t index) {
281  if (!buffer_ || index >= size_) {
282  throw std::out_of_range("Buffer index out of range");
283  }
284  return buffer_[index];
285 }
286 
287 const uint8_t& PooledBuffer::at(size_t index) const {
288  if (!buffer_ || index >= size_) {
289  throw std::out_of_range("Buffer index out of range");
290  }
291  return buffer_[index];
292 }
293 
294 void PooledBuffer::check_bounds(size_t index) const {
295  if (!buffer_ || index >= size_) {
296  throw std::out_of_range("Buffer index out of range");
297  }
298 }
299 
300 } // namespace memory
301 } // namespace unilink
#define MEMORY_TRACK_DEALLOCATION(ptr)
#define MEMORY_TRACK_ALLOCATION(ptr, size)