Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 : : * Author: Thun Lu <thun.lu@zohomail.cn>
4 : : * Repo: https://github.com/thun-res/vlink
5 : : * _ __ __ _ __
6 : : * | | / / / / (_) ____ / /__
7 : : * | | / / / / / / / __ \ / //_/
8 : : * | |/ / / /___ / / / / / / / ,<
9 : : * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 : : *
11 : : * Licensed under the Apache License, Version 2.0 (the "License");
12 : : * you may not use this file except in compliance with the License.
13 : : * You may obtain a copy of the License at
14 : : *
15 : : * http://www.apache.org/licenses/LICENSE-2.0
16 : : *
17 : : * Unless required by applicable law or agreed to in writing, software
18 : : * distributed under the License is distributed on an "AS IS" BASIS,
19 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 : : * See the License for the specific language governing permissions and
21 : : * limitations under the License.
22 : : */
23 : :
24 : : #include "./base/memory_pool.h"
25 : :
26 : : #include <algorithm>
27 : : #include <array>
28 : : #include <atomic>
29 : : #include <charconv>
30 : : #include <cstddef>
31 : : #include <cstdint>
32 : : #include <exception>
33 : : #include <mutex>
34 : : #include <new>
35 : : #include <string>
36 : : #include <system_error>
37 : : #include <utility>
38 : : #include <vector>
39 : :
40 : : #include "./base/logger.h"
41 : : #include "./base/spin_lock.h"
42 : : #include "./base/utils.h"
43 : :
44 : : #define MEMORY_POOL_NEVER_DELETE 0
45 : :
46 : : namespace vlink {
47 : :
48 : : static constexpr int kMinMemoryLevel = 0;
49 : : static constexpr int kMaxMemoryLevel = 9;
50 : : static constexpr int kDefaultMemoryLevel = 3;
51 : : static constexpr size_t kMaxTierCount = 20U;
52 : : static constexpr size_t kMaxLevelCount = 10U;
53 : : static constexpr size_t kInitialBlocksPerChunk = 1U;
54 : : static constexpr size_t kInitialChunksReserve = 16U;
55 : : static constexpr size_t kInitialChunkBytesTarget = 64U * 1024U;
56 : : static constexpr size_t kTierShardCount = 8U;
57 : : static constexpr size_t kDefaultBatchSize = 16U;
58 : : static constexpr uint32_t kShardingContentionThreshold = 8U;
59 : :
60 : : // clang-format off
61 : : static constexpr MemoryPool::Tier kDefaultTierTable[kMaxLevelCount][kMaxTierCount] = {
62 : : // L0 ~ 0 MiB. (bypass; all entries are sentinels)
63 : : {
64 : : {32U, 0U},
65 : : {64U, 0U},
66 : : {128U, 0U},
67 : : {256U, 0U},
68 : : {512U, 0U},
69 : : {1U * 1024U, 0U},
70 : : {2U * 1024U, 0U},
71 : : {4U * 1024U, 0U},
72 : : {8U * 1024U, 0U},
73 : : {16U * 1024U, 0U},
74 : : {32U * 1024U, 0U},
75 : : {64U * 1024U, 0U},
76 : : {128U * 1024U, 0U},
77 : : {256U * 1024U, 0U},
78 : : {512U * 1024U, 0U},
79 : : {1U * 1024U * 1024U, 0U},
80 : : {4U * 1024U * 1024U, 0U},
81 : : {8U * 1024U * 1024U, 0U},
82 : : {16U * 1024U * 1024U, 0U},
83 : : },
84 : : // L1 ~ 4 MiB.
85 : : {
86 : : {32U, 8U * 1024U},
87 : : {64U, 4U * 1024U},
88 : : {128U, 2U * 1024U},
89 : : {256U, 1U * 1024U},
90 : : {512U, 512U},
91 : : {1U * 1024U, 256U},
92 : : {2U * 1024U, 128U},
93 : : {4U * 1024U, 64U},
94 : : {8U * 1024U, 32U},
95 : : {16U * 1024U, 16U},
96 : : {32U * 1024U, 8U},
97 : : {64U * 1024U, 4U},
98 : : {128U * 1024U, 2U},
99 : : {256U * 1024U, 1U},
100 : : {512U * 1024U, 1U},
101 : : {1U * 1024U * 1024U, 0U},
102 : : {4U * 1024U * 1024U, 0U},
103 : : {8U * 1024U * 1024U, 0U},
104 : : {16U * 1024U * 1024U, 0U},
105 : : },
106 : : // L2 ~ 8.5 MiB.
107 : : {
108 : : {32U, 16U * 1024U},
109 : : {64U, 8U * 1024U},
110 : : {128U, 4U * 1024U},
111 : : {256U, 2U * 1024U},
112 : : {512U, 1U * 1024U},
113 : : {1U * 1024U, 512U},
114 : : {2U * 1024U, 256U},
115 : : {4U * 1024U, 128U},
116 : : {8U * 1024U, 64U},
117 : : {16U * 1024U, 32U},
118 : : {32U * 1024U, 16U},
119 : : {64U * 1024U, 8U},
120 : : {128U * 1024U, 4U},
121 : : {256U * 1024U, 2U},
122 : : {512U * 1024U, 1U},
123 : : {1U * 1024U * 1024U, 1U},
124 : : {4U * 1024U * 1024U, 0U},
125 : : {8U * 1024U * 1024U, 0U},
126 : : {16U * 1024U * 1024U, 0U},
127 : : },
128 : : // L3 ~ 16 MiB. (Default)
129 : : {
130 : : {32U, 32U * 1024U},
131 : : {64U, 16U * 1024U},
132 : : {128U, 8U * 1024U},
133 : : {256U, 4U * 1024U},
134 : : {512U, 2U * 1024U},
135 : : {1U * 1024U, 1U * 1024U},
136 : : {2U * 1024U, 512U},
137 : : {4U * 1024U, 256U},
138 : : {8U * 1024U, 128U},
139 : : {16U * 1024U, 64U},
140 : : {32U * 1024U, 32U},
141 : : {64U * 1024U, 16U},
142 : : {128U * 1024U, 8U},
143 : : {256U * 1024U, 4U},
144 : : {512U * 1024U, 2U},
145 : : {1U * 1024U * 1024U, 1U},
146 : : {4U * 1024U * 1024U, 0U},
147 : : {8U * 1024U * 1024U, 0U},
148 : : {16U * 1024U * 1024U, 0U},
149 : : },
150 : : // L4 ~ 42 MiB.
151 : : {
152 : : {32U, 64U * 1024U},
153 : : {64U, 32U * 1024U},
154 : : {128U, 16U * 1024U},
155 : : {256U, 8U * 1024U},
156 : : {512U, 4U * 1024U},
157 : : {1U * 1024U, 2U * 1024U},
158 : : {2U * 1024U, 1U * 1024U},
159 : : {4U * 1024U, 512U},
160 : : {8U * 1024U, 256U},
161 : : {16U * 1024U, 128U},
162 : : {32U * 1024U, 64U},
163 : : {64U * 1024U, 32U},
164 : : {128U * 1024U, 16U},
165 : : {256U * 1024U, 8U},
166 : : {512U * 1024U, 4U},
167 : : {1U * 1024U * 1024U, 4U},
168 : : {4U * 1024U * 1024U, 2U},
169 : : {8U * 1024U * 1024U, 0U},
170 : : {16U * 1024U * 1024U, 0U},
171 : : },
172 : : // L5 ~ 92 MiB.
173 : : {
174 : : {32U, 128U * 1024U},
175 : : {64U, 64U * 1024U},
176 : : {128U, 32U * 1024U},
177 : : {256U, 16U * 1024U},
178 : : {512U, 8U * 1024U},
179 : : {1U * 1024U, 4U * 1024U},
180 : : {2U * 1024U, 2U * 1024U},
181 : : {4U * 1024U, 1U * 1024U},
182 : : {8U * 1024U, 512U},
183 : : {16U * 1024U, 256U},
184 : : {32U * 1024U, 128U},
185 : : {64U * 1024U, 64U},
186 : : {128U * 1024U, 32U},
187 : : {256U * 1024U, 16U},
188 : : {512U * 1024U, 8U},
189 : : {1U * 1024U * 1024U, 8U},
190 : : {4U * 1024U * 1024U, 4U},
191 : : {8U * 1024U * 1024U, 1U},
192 : : {16U * 1024U * 1024U, 0U},
193 : : },
194 : : // L6 ~ 200 MiB.
195 : : {
196 : : {32U, 256U * 1024U},
197 : : {64U, 128U * 1024U},
198 : : {128U, 64U * 1024U},
199 : : {256U, 32U * 1024U},
200 : : {512U, 16U * 1024U},
201 : : {1U * 1024U, 8U * 1024U},
202 : : {2U * 1024U, 4U * 1024U},
203 : : {4U * 1024U, 2U * 1024U},
204 : : {8U * 1024U, 1U * 1024U},
205 : : {16U * 1024U, 512U},
206 : : {32U * 1024U, 256U},
207 : : {64U * 1024U, 128U},
208 : : {128U * 1024U, 64U},
209 : : {256U * 1024U, 32U},
210 : : {512U * 1024U, 16U},
211 : : {1U * 1024U * 1024U, 16U},
212 : : {4U * 1024U * 1024U, 8U},
213 : : {8U * 1024U * 1024U, 2U},
214 : : {16U * 1024U * 1024U, 1U},
215 : : },
216 : : // L7 ~ 264 MiB.
217 : : {
218 : : {32U, 256U * 1024U},
219 : : {64U, 128U * 1024U},
220 : : {128U, 64U * 1024U},
221 : : {256U, 32U * 1024U},
222 : : {512U, 16U * 1024U},
223 : : {1U * 1024U, 8U * 1024U},
224 : : {2U * 1024U, 4U * 1024U},
225 : : {4U * 1024U, 2U * 1024U},
226 : : {8U * 1024U, 1U * 1024U},
227 : : {16U * 1024U, 512U},
228 : : {32U * 1024U, 256U},
229 : : {64U * 1024U, 128U},
230 : : {128U * 1024U, 64U},
231 : : {256U * 1024U, 32U},
232 : : {512U * 1024U, 16U},
233 : : {1U * 1024U * 1024U, 16U},
234 : : {4U * 1024U * 1024U, 16U},
235 : : {8U * 1024U * 1024U, 4U},
236 : : {16U * 1024U * 1024U, 2U},
237 : : },
238 : : // L8 ~ 528 MiB.
239 : : {
240 : : {32U, 512U * 1024U},
241 : : {64U, 256U * 1024U},
242 : : {128U, 128U * 1024U},
243 : : {256U, 64U * 1024U},
244 : : {512U, 32U * 1024U},
245 : : {1U * 1024U, 16U * 1024U},
246 : : {2U * 1024U, 8U * 1024U},
247 : : {4U * 1024U, 4U * 1024U},
248 : : {8U * 1024U, 2U * 1024U},
249 : : {16U * 1024U, 1U * 1024U},
250 : : {32U * 1024U, 512U},
251 : : {64U * 1024U, 256U},
252 : : {128U * 1024U, 128U},
253 : : {256U * 1024U, 64U},
254 : : {512U * 1024U, 32U},
255 : : {1U * 1024U * 1024U, 32U},
256 : : {4U * 1024U * 1024U, 32U},
257 : : {8U * 1024U * 1024U, 8U},
258 : : {16U * 1024U * 1024U, 4U},
259 : : },
260 : : // L9 ~ 656 MiB.
261 : : {
262 : : {32U, 512U * 1024U},
263 : : {64U, 256U * 1024U},
264 : : {128U, 128U * 1024U},
265 : : {256U, 64U * 1024U},
266 : : {512U, 32U * 1024U},
267 : : {1U * 1024U, 16U * 1024U},
268 : : {2U * 1024U, 8U * 1024U},
269 : : {4U * 1024U, 4U * 1024U},
270 : : {8U * 1024U, 2U * 1024U},
271 : : {16U * 1024U, 1U * 1024U},
272 : : {32U * 1024U, 512U},
273 : : {64U * 1024U, 256U},
274 : : {128U * 1024U, 128U},
275 : : {256U * 1024U, 64U},
276 : : {512U * 1024U, 32U},
277 : : {1U * 1024U * 1024U, 32U},
278 : : {4U * 1024U * 1024U, 32U},
279 : : {8U * 1024U * 1024U, 16U},
280 : : {16U * 1024U * 1024U, 8U},
281 : : }
282 : : };
283 : : // clang-format on
284 : :
285 : : struct MemoryFreeNode final {
286 : : MemoryFreeNode* next{nullptr};
287 : : };
288 : :
289 : : struct MemoryChunk final {
290 : : void* ptr{nullptr};
291 : : size_t bytes{0};
292 : : };
293 : :
294 : : struct alignas(64) MemoryTierShard final {
295 : : SpinLock mtx;
296 : : MemoryFreeNode* free_list_head{nullptr};
297 : : std::atomic<uint64_t> hit_count{0};
298 : : std::atomic<uint64_t> deallocate_count{0};
299 : : };
300 : :
301 : : // NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
302 : : struct alignas(64) MemoryTierState final {
303 : : size_t max_size{0};
304 : : size_t block_size{0};
305 : : size_t blocks_per_chunk{0};
306 : : size_t next_chunk_blocks{0};
307 : : size_t initial_chunk_blocks{0};
308 : : size_t batch_size{kDefaultBatchSize};
309 : :
310 : : std::array<MemoryTierShard, kTierShardCount> shards;
311 : : std::vector<MemoryChunk> chunks;
312 : : std::mutex grow_mtx;
313 : : std::atomic<bool> sharded{false};
314 : : std::atomic<uint32_t> contention_count{0U};
315 : :
316 : : std::atomic<uint64_t> chunk_count{0};
317 : : std::atomic<uint64_t> upstream_alloc_count{0};
318 : : std::atomic<uint64_t> upstream_alloc_bytes{0};
319 : : };
320 : :
321 : : class MemoryTierShardLockGuard final {
322 : : public:
323 : 1346 : explicit MemoryTierShardLockGuard(MemoryTierState& state) noexcept : state_(state) {
324 [ + + ]: 12114 : for (auto& shard : state_.shards) {
325 : 10768 : shard.mtx.lock();
326 : : }
327 : 1346 : }
328 : :
329 : 1346 : ~MemoryTierShardLockGuard() noexcept {
330 [ + + ]: 12114 : for (size_t index = state_.shards.size(); index > 0U; --index) {
331 : 10768 : state_.shards[index - 1U].mtx.unlock();
332 : : }
333 : 1346 : }
334 : :
335 : : private:
336 : : MemoryTierState& state_;
337 : :
338 : : VLINK_DISALLOW_COPY_AND_ASSIGN(MemoryTierShardLockGuard)
339 : : };
340 : :
341 : : struct MemoryAllocCounters final {
342 : : std::atomic<uint64_t> count{0};
343 : : std::atomic<uint64_t> bytes{0};
344 : : };
345 : :
346 [ + # + # ]: 90123 : static constexpr bool is_power_of_two(size_t x) noexcept { return x != 0 && ((x & (x - 1U)) == 0U); }
347 : :
348 : 3588 : static constexpr size_t round_up(size_t value, size_t alignment) noexcept {
349 : 3588 : return (value + alignment - 1U) & ~(alignment - 1U);
350 : : }
351 : :
352 : : static constexpr bool default_tier_table_well_formed() noexcept {
353 : : // NOLINTNEXTLINE(modernize-loop-convert)
354 : : for (size_t level = 0; level < kMaxLevelCount; ++level) {
355 : : size_t prev_max_size = 0U;
356 : :
357 : : for (size_t t = 0; t < kMaxTierCount; ++t) {
358 : : const size_t max_size = kDefaultTierTable[level][t].max_size;
359 : :
360 : : if (max_size == 0U) {
361 : : break;
362 : : }
363 : :
364 : : if (max_size < sizeof(MemoryFreeNode)) {
365 : : return false;
366 : : }
367 : :
368 : : if (t > 0U && max_size <= prev_max_size) {
369 : : return false;
370 : : }
371 : :
372 : : prev_max_size = max_size;
373 : : }
374 : : }
375 : :
376 : : return true;
377 : : }
378 : :
379 : : static_assert(default_tier_table_well_formed(),
380 : : "MemoryPool: kDefaultTierTable contains a malformed row "
381 : : "(undersized tier or non-monotonic max_size)");
382 : :
383 : : static std::atomic<size_t> next_tier_shard{0U};
384 : : static thread_local size_t current_tier_shard_plus_one = 0U;
385 : :
386 : 64278 : static size_t current_tier_shard() noexcept {
387 [ + + ]: 64278 : if VUNLIKELY (current_tier_shard_plus_one == 0U) {
388 : 12 : current_tier_shard_plus_one = next_tier_shard.fetch_add(1U, std::memory_order_relaxed) % kTierShardCount + 1U;
389 : : }
390 : :
391 : 64278 : return current_tier_shard_plus_one - 1U;
392 : : }
393 : :
394 : 33833 : static MemoryFreeNode* pop_free_node(MemoryTierShard& shard) noexcept {
395 : 33833 : SpinLockGuard lock(shard.mtx);
396 : :
397 [ + + ]: 34267 : if (shard.free_list_head == nullptr) {
398 : 1537 : return nullptr;
399 : : }
400 : :
401 : 32730 : MemoryFreeNode* node = shard.free_list_head;
402 : 32730 : shard.free_list_head = node->next;
403 : :
404 : 32730 : return node;
405 : 34267 : }
406 : :
407 : 1535 : static MemoryFreeNode* steal_free_nodes(MemoryTierState& state, size_t target_index) noexcept {
408 : 1535 : MemoryTierShard& target = state.shards[target_index];
409 : :
410 [ + + ]: 11488 : for (size_t offset = 1U; offset < kTierShardCount; ++offset) {
411 : 10106 : MemoryTierShard& source = state.shards[(target_index + offset) % kTierShardCount];
412 : 10106 : MemoryFreeNode* first = nullptr;
413 : 10106 : MemoryFreeNode* last = nullptr;
414 : :
415 : : {
416 : 10106 : SpinLockGuard source_lock(source.mtx);
417 : :
418 [ + + ]: 10126 : if (source.free_list_head == nullptr) {
419 : 9966 : continue;
420 : : }
421 : :
422 : 160 : first = source.free_list_head;
423 : 160 : last = first;
424 : :
425 : 160 : size_t count = 1U;
426 : :
427 [ + + + + ]: 1094 : while (count < state.batch_size && last->next != nullptr) {
428 : 934 : last = last->next;
429 : 934 : ++count;
430 : : }
431 : :
432 : 160 : source.free_list_head = last->next;
433 : 160 : last->next = nullptr;
434 [ + + ]: 10126 : }
435 : :
436 : 160 : MemoryFreeNode* cached = first->next;
437 : 160 : first->next = nullptr;
438 : :
439 [ + + ]: 160 : if (cached != nullptr) {
440 : 108 : SpinLockGuard target_lock(target.mtx);
441 : 108 : last->next = target.free_list_head;
442 : 108 : target.free_list_head = cached;
443 : 108 : }
444 : :
445 : 160 : return first;
446 : : }
447 : :
448 : 1382 : return nullptr;
449 : : }
450 : :
451 : 34014 : static MemoryFreeNode* try_allocate_from_shards(MemoryTierState& state, size_t shard_index) noexcept {
452 : 34014 : MemoryFreeNode* node = pop_free_node(state.shards[shard_index]);
453 : :
454 [ + + ]: 33936 : if VLIKELY (node != nullptr) {
455 : 32401 : return node;
456 : : }
457 : :
458 : 1535 : return steal_free_nodes(state, shard_index);
459 : : }
460 : :
461 : : // state.grow_mtx must be held. When allocated is non-null, one node is removed from the new chunk.
462 : 674 : static bool grow_tier_chunk(MemoryTierState& state, size_t shard_index, MemoryFreeNode** allocated) noexcept {
463 : 674 : size_t blocks = state.next_chunk_blocks;
464 : :
465 [ - + ]: 674 : if VUNLIKELY (blocks > state.blocks_per_chunk) {
466 : : blocks = state.blocks_per_chunk; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
467 : : }
468 : :
469 : 674 : const size_t block_size = state.block_size;
470 : 674 : const size_t chunk_bytes = block_size * blocks;
471 : :
472 [ - + ]: 674 : if VUNLIKELY (chunk_bytes / block_size != blocks) {
473 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
474 : : }
475 : :
476 : 674 : void* ptr = ::operator new(chunk_bytes, std::align_val_t{MemoryPool::kBlockAlignment}, std::nothrow);
477 : :
478 [ - + ]: 674 : if VUNLIKELY (ptr == nullptr) {
479 : : return false; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
480 : : }
481 : :
482 : 674 : auto* base = static_cast<std::byte*>(ptr);
483 : 674 : auto* local_tail = ::new (base + (blocks - 1U) * block_size) MemoryFreeNode{nullptr};
484 : 674 : MemoryFreeNode* local_head = local_tail;
485 : :
486 [ + + ]: 285099 : for (size_t i = blocks - 1U; i > 0; --i) {
487 : 284425 : local_head = ::new (base + (i - 1U) * block_size) MemoryFreeNode{local_head};
488 : : }
489 : :
490 : : try {
491 [ + - ]: 674 : state.chunks.push_back(MemoryChunk{ptr, chunk_bytes});
492 [ - - ]: 0 : } catch (std::exception&) {
493 : : // LCOV_EXCL_START GCOVR_EXCL_START
494 : : ::operator delete(ptr, chunk_bytes, std::align_val_t{MemoryPool::kBlockAlignment});
495 : : return false;
496 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
497 : 0 : }
498 : :
499 : 674 : state.upstream_alloc_count.fetch_add(1, std::memory_order_relaxed);
500 : 674 : state.upstream_alloc_bytes.fetch_add(chunk_bytes, std::memory_order_relaxed);
501 : 674 : state.chunk_count.fetch_add(1, std::memory_order_relaxed);
502 : :
503 : : {
504 : 674 : MemoryTierShard& shard = state.shards[shard_index];
505 : 674 : SpinLockGuard lock(shard.mtx);
506 : :
507 : 674 : local_tail->next = shard.free_list_head;
508 : 674 : shard.free_list_head = local_head;
509 : :
510 [ + + ]: 674 : if (allocated != nullptr) {
511 : 671 : *allocated = shard.free_list_head;
512 : 671 : shard.free_list_head = (*allocated)->next;
513 : 671 : (*allocated)->next = nullptr;
514 : : }
515 : 674 : }
516 : :
517 : 674 : const size_t doubled = blocks * 2U;
518 [ + + ]: 674 : const size_t target = (doubled < blocks || doubled > state.blocks_per_chunk)
519 [ + - ]: 1348 : ? state.blocks_per_chunk
520 : : : doubled; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
521 : :
522 [ + + ]: 674 : if (target > state.next_chunk_blocks) {
523 : 386 : state.next_chunk_blocks = target;
524 : : }
525 : :
526 : 674 : return true;
527 : : }
528 : :
529 : 46005 : static void* tier_allocate(MemoryTierState& state, size_t& shard_index) noexcept {
530 : 46005 : shard_index = 0U;
531 : 46005 : bool sharded = state.sharded.load(std::memory_order_relaxed);
532 : :
533 [ + + ]: 46737 : if (!sharded) {
534 : 13684 : MemoryTierShard& primary = state.shards.front();
535 : :
536 [ + + ]: 13674 : if (primary.mtx.try_lock()) {
537 : 13646 : MemoryFreeNode* node = primary.free_list_head;
538 : :
539 [ + + ]: 13646 : if VLIKELY (node != nullptr) {
540 : 13205 : primary.free_list_head = node->next;
541 : : }
542 : :
543 : 13646 : primary.mtx.unlock();
544 : :
545 [ + + ]: 13650 : if VLIKELY (node != nullptr) {
546 : 13209 : return node;
547 : : }
548 : : } else {
549 : 28 : const uint32_t contentions = state.contention_count.fetch_add(1U, std::memory_order_relaxed) + 1U;
550 : :
551 [ + + ]: 28 : if (contentions >= kShardingContentionThreshold) {
552 : 3 : state.sharded.store(true, std::memory_order_relaxed);
553 : 3 : sharded = true;
554 : : }
555 : : }
556 : : }
557 : :
558 [ + + ]: 33522 : if (sharded) {
559 : 32978 : shard_index = current_tier_shard();
560 : : }
561 : :
562 [ + + ]: 33440 : if (MemoryFreeNode* node = try_allocate_from_shards(state, shard_index)) {
563 : 32659 : return node;
564 : : }
565 : :
566 : 515 : std::lock_guard grow_lock(state.grow_mtx);
567 : :
568 [ + + ]: 708 : if (MemoryFreeNode* node = try_allocate_from_shards(state, shard_index)) {
569 : 37 : return node;
570 : : }
571 : :
572 : 671 : MemoryFreeNode* node = nullptr;
573 : :
574 [ - + ]: 671 : if VUNLIKELY (!grow_tier_chunk(state, shard_index, &node)) {
575 : 0 : return nullptr;
576 : : }
577 : :
578 : 671 : return node;
579 : 708 : }
580 : :
581 : 46088 : static size_t tier_deallocate(MemoryTierState& state, void* p) noexcept {
582 [ + + ]: 46088 : if (!state.sharded.load(std::memory_order_relaxed)) {
583 : 13624 : MemoryTierShard& primary = state.shards.front();
584 : :
585 [ + + ]: 13622 : if (primary.mtx.try_lock()) {
586 : 13597 : primary.free_list_head = ::new (p) MemoryFreeNode{primary.free_list_head};
587 : 13597 : primary.mtx.unlock();
588 : :
589 : 13593 : return 0U;
590 : : }
591 : :
592 : 31 : const uint32_t contentions = state.contention_count.fetch_add(1U, std::memory_order_relaxed) + 1U;
593 : :
594 [ + + ]: 31 : if (contentions < kShardingContentionThreshold) {
595 : 29 : SpinLockGuard lock(primary.mtx);
596 : 29 : primary.free_list_head = ::new (p) MemoryFreeNode{primary.free_list_head};
597 : :
598 : 29 : return 0U;
599 : 29 : }
600 : :
601 : 2 : state.sharded.store(true, std::memory_order_relaxed);
602 : : }
603 : :
604 : 33063 : const size_t shard_index = current_tier_shard();
605 : 32914 : MemoryTierShard& shard = state.shards[shard_index];
606 : 32705 : SpinLockGuard lock(shard.mtx);
607 : :
608 : 33138 : auto* node = ::new (p) MemoryFreeNode{shard.free_list_head};
609 : :
610 : 33060 : shard.free_list_head = node;
611 : :
612 : 33060 : return shard_index;
613 : 33060 : }
614 : :
615 : 3 : static void prealloc_full_quota(MemoryTierState& state) noexcept {
616 : 3 : std::lock_guard grow_lock(state.grow_mtx);
617 : :
618 : 3 : state.next_chunk_blocks = state.blocks_per_chunk;
619 : 3 : const bool ok = grow_tier_chunk(state, 0U, nullptr);
620 : :
621 [ - + ]: 3 : if VUNLIKELY (!ok) {
622 : : state.next_chunk_blocks = state.initial_chunk_blocks; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
623 : : }
624 : :
625 [ - + ]: 3 : if VUNLIKELY (!ok) {
626 : 0 : CLOG_W("MemoryPool: prealloc failed for tier (max_size=%zu, blocks_per_chunk=%zu); tier reverts to lazy growth.",
627 : : state.max_size, state.blocks_per_chunk);
628 : : }
629 : 3 : }
630 : :
631 : 249 : static bool validate_tiers_log(const std::vector<MemoryPool::Tier>& tiers) noexcept {
632 : : static constexpr size_t kMaxTierSize = SIZE_MAX - MemoryPool::kBlockAlignment + 1U;
633 : :
634 [ + + ]: 249 : if VUNLIKELY (tiers.size() > kMaxTierCount) {
635 : 2 : CLOG_E("MemoryPool: tier count %zu exceeds max %zu; falling back to default pyramid.", tiers.size(), kMaxTierCount);
636 : 1 : return false;
637 : : }
638 : :
639 [ + + ]: 4423 : for (size_t i = 0; i < tiers.size(); ++i) {
640 [ + + ]: 4180 : if VUNLIKELY (tiers[i].max_size == 0) {
641 : 2 : CLOG_E("MemoryPool: tier %zu has max_size == 0; falling back to default pyramid.", i);
642 : 5 : return false;
643 : : }
644 : :
645 [ + + ]: 4179 : if VUNLIKELY (tiers[i].max_size < sizeof(MemoryFreeNode)) {
646 : 2 : CLOG_E(
647 : : "MemoryPool: tier %zu max_size (%zu) is below the minimum block size %zu; "
648 : : "falling back to default pyramid.",
649 : : i, tiers[i].max_size, sizeof(MemoryFreeNode));
650 : 1 : return false;
651 : : }
652 : :
653 [ + + ]: 4178 : if VUNLIKELY (tiers[i].max_size > kMaxTierSize) {
654 : 2 : CLOG_E("MemoryPool: tier %zu max_size overflows after alignment rounding; falling back.", i);
655 : 1 : return false;
656 : : }
657 : :
658 [ + + + + : 4177 : if VUNLIKELY (i > 0 && tiers[i].max_size <= tiers[i - 1].max_size) {
+ + ]
659 : 4 : CLOG_E("MemoryPool: tier %zu max_size is not strictly increasing; falling back to default pyramid.", i);
660 : 2 : return false;
661 : : }
662 : : }
663 : :
664 : 243 : return true;
665 : : }
666 : :
667 : 224 : static MemoryPool::Config create_memory_config(int level, bool prealloc) {
668 [ + - + + : 224 : if VUNLIKELY (level < kMinMemoryLevel || level > kMaxMemoryLevel) {
+ + ]
669 [ + - + - ]: 2 : CLOG_W("MemoryPool: level %d out of range [%d, %d], clamped.", level, kMinMemoryLevel, kMaxMemoryLevel);
670 [ - + ]: 1 : level = (level < kMinMemoryLevel) ? kMinMemoryLevel : kMaxMemoryLevel;
671 : : }
672 : :
673 : 224 : const auto row_index = static_cast<size_t>(level - kMinMemoryLevel);
674 : 224 : const auto& row = kDefaultTierTable[row_index];
675 : :
676 : 224 : MemoryPool::Config config;
677 : 224 : config.prealloc = prealloc;
678 [ + - ]: 224 : config.tiers.reserve(kMaxTierCount);
679 : :
680 [ + - + + ]: 4480 : for (size_t i = 0; i < kMaxTierCount && row[i].max_size != 0; ++i) {
681 [ + - ]: 4256 : config.tiers.emplace_back(row[i]);
682 : : }
683 : :
684 : 224 : return config;
685 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
686 : :
687 : : struct MemoryPool::Impl final { // NOLINT(clang-analyzer-optin.performance.Padding)
688 : : alignas(64) size_t dispatch_max_sizes[kMaxTierCount]{};
689 : : MemoryTierState* dispatch_states[kMaxTierCount]{};
690 : : size_t dispatch_count{0};
691 : :
692 : : MemoryTierState* tier_states[kMaxTierCount]{};
693 : : size_t tier_count{0};
694 : : std::vector<std::unique_ptr<MemoryTierState>> owned_states;
695 : : MemoryAllocCounters oversized_alloc;
696 : :
697 : : std::atomic<uint64_t> oversized_dealloc_count{0};
698 : : };
699 : :
700 [ + - ]: 4 : MemoryPool::MemoryPool() : MemoryPool(Config{}) {}
701 : :
702 [ + - ]: 16 : MemoryPool::MemoryPool(int level, bool prealloc) : MemoryPool(create_memory_config(level, prealloc)) {}
703 : :
704 : 255 : MemoryPool::MemoryPool(const Config& config) : impl_(std::make_unique<Impl>()) {
705 [ + + ]: 255 : if (config.tiers.empty()) {
706 : 6 : impl_->tier_count = 0;
707 : 6 : return;
708 : : }
709 : :
710 : 249 : std::vector<Tier> fallback;
711 : 249 : const bool use_caller = validate_tiers_log(config.tiers);
712 : :
713 [ + + ]: 249 : if VUNLIKELY (!use_caller) {
714 : 6 : const auto& row = kDefaultTierTable[kDefaultMemoryLevel - kMinMemoryLevel];
715 [ + - ]: 6 : fallback.assign(row, row + kMaxTierCount);
716 : : }
717 : :
718 [ + + ]: 249 : const std::vector<Tier>& source = use_caller ? config.tiers : fallback;
719 [ + + ]: 249 : const size_t batch_size = config.batch_size == 0U ? kDefaultBatchSize : config.batch_size;
720 : :
721 [ + + ]: 249 : if VUNLIKELY (config.batch_size == 0U) {
722 [ + - + - ]: 2 : CLOG_W("MemoryPool: batch_size is 0; fallback to %zu.", kDefaultBatchSize);
723 : : }
724 : :
725 [ + - ]: 249 : impl_->owned_states.reserve(source.size());
726 : :
727 : 249 : size_t live = 0;
728 : 249 : size_t dispatch = 0;
729 : :
730 [ + + ]: 4542 : for (const auto& cfg : source) {
731 [ + + ]: 4293 : if VUNLIKELY (cfg.max_size == 0U) {
732 : 705 : continue;
733 : : }
734 : :
735 : 4287 : impl_->dispatch_max_sizes[dispatch] = cfg.max_size;
736 : :
737 [ + + ]: 4287 : if VUNLIKELY (cfg.blocks_per_chunk == 0U) {
738 : 699 : impl_->dispatch_states[dispatch] = nullptr;
739 : 699 : ++dispatch;
740 : 699 : continue;
741 : : }
742 : :
743 [ + - ]: 3588 : auto state = std::make_unique<MemoryTierState>();
744 : 3588 : state->max_size = cfg.max_size;
745 : 3588 : state->blocks_per_chunk = cfg.blocks_per_chunk;
746 : 3588 : state->batch_size = batch_size;
747 [ + - ]: 3588 : state->chunks.reserve(kInitialChunksReserve);
748 : 3588 : state->block_size = round_up(cfg.max_size, kBlockAlignment);
749 : :
750 [ + - ]: 3588 : size_t initial = (state->block_size > 0U) ? (kInitialChunkBytesTarget / state->block_size) : kInitialBlocksPerChunk;
751 : :
752 [ + + ]: 3588 : if (initial < kInitialBlocksPerChunk) {
753 : 887 : initial = kInitialBlocksPerChunk;
754 : : }
755 : :
756 [ + + ]: 3588 : if (initial > state->blocks_per_chunk) {
757 : 49 : initial = state->blocks_per_chunk;
758 : : }
759 : :
760 : 3588 : state->initial_chunk_blocks = initial;
761 : 3588 : state->next_chunk_blocks = initial;
762 : :
763 : 3588 : impl_->tier_states[live] = state.get();
764 : 3588 : impl_->dispatch_states[dispatch] = state.get();
765 [ + - ]: 3588 : impl_->owned_states.emplace_back(std::move(state));
766 : :
767 : 3588 : ++live;
768 : 3588 : ++dispatch;
769 : 3588 : }
770 : :
771 : 249 : impl_->dispatch_count = dispatch;
772 : 249 : impl_->tier_count = live;
773 : :
774 [ + + ]: 249 : if (config.prealloc) {
775 [ + + ]: 5 : for (auto& state : impl_->owned_states) {
776 : 3 : prealloc_full_quota(*state);
777 : : }
778 : : }
779 : 249 : }
780 : :
781 : 255 : MemoryPool::~MemoryPool() {
782 [ + + ]: 3843 : for (auto& state : impl_->owned_states) {
783 [ + + ]: 4008 : for (const MemoryChunk& chunk : state->chunks) {
784 : 420 : ::operator delete(chunk.ptr, chunk.bytes, std::align_val_t{kBlockAlignment});
785 : : }
786 : :
787 : 3588 : state->chunks.clear();
788 : :
789 [ + + ]: 32292 : for (auto& shard : state->shards) {
790 : 28704 : shard.free_list_head = nullptr;
791 : : }
792 : : }
793 : 255 : }
794 : :
795 : 46038 : void* MemoryPool::allocate(size_t bytes, size_t alignment) noexcept {
796 [ + + ]: 46038 : if VUNLIKELY (!is_power_of_two(alignment)) {
797 : 2 : CLOG_E("MemoryPool::allocate: alignment %zu is not a power of two; returning nullptr.", alignment);
798 : 757 : return nullptr;
799 : : }
800 : :
801 : 45950 : const size_t idx = find_tier(bytes);
802 : :
803 [ + # + + : 46459 : if VUNLIKELY (idx == kMaxTierCount || alignment > kBlockAlignment || impl_->dispatch_states[idx] == nullptr) {
+ # + + +
+ ]
804 : 85 : void* p = ::operator new(bytes, std::align_val_t{alignment}, std::nothrow);
805 : :
806 [ - + ]: 85 : if VUNLIKELY (p == nullptr) {
807 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
808 : : }
809 : :
810 : 85 : impl_->oversized_alloc.count.fetch_add(1, std::memory_order_relaxed);
811 : 85 : impl_->oversized_alloc.bytes.fetch_add(bytes, std::memory_order_relaxed);
812 : :
813 : 85 : return p;
814 : : }
815 : :
816 : 45849 : MemoryTierState& state = *impl_->dispatch_states[idx];
817 : 46168 : size_t shard_index = 0U;
818 : 46168 : void* block = tier_allocate(state, shard_index);
819 : :
820 [ - + ]: 46375 : if VUNLIKELY (block == nullptr) {
821 : : return nullptr; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
822 : : }
823 : :
824 : 46375 : state.shards[shard_index].hit_count.fetch_add(1, std::memory_order_relaxed);
825 : :
826 : 46217 : return block;
827 : : }
828 : :
829 : 45971 : void MemoryPool::deallocate(void* p, size_t bytes, size_t alignment) noexcept {
830 [ # + ]: 45971 : if VUNLIKELY (!is_power_of_two(alignment)) {
831 : 0 : CLOG_E("MemoryPool::deallocate: alignment %zu is not a power of two; leaking %p.", alignment, p);
832 : 1 : return;
833 : : }
834 : :
835 [ + + ]: 46062 : if VUNLIKELY (p == nullptr) {
836 : 2 : return;
837 : : }
838 : :
839 : 46060 : const size_t idx = find_tier(bytes);
840 : :
841 [ + # + + : 46471 : if VUNLIKELY (idx == kMaxTierCount || alignment > kBlockAlignment || impl_->dispatch_states[idx] == nullptr) {
+ + # + +
+ ]
842 : 84 : ::operator delete(p, bytes, std::align_val_t{alignment});
843 : 84 : impl_->oversized_dealloc_count.fetch_add(1, std::memory_order_relaxed);
844 : :
845 : 84 : return;
846 : : }
847 : :
848 : 46083 : MemoryTierState& state = *impl_->dispatch_states[idx];
849 : 46185 : const size_t shard_index = tier_deallocate(state, p);
850 : 46084 : state.shards[shard_index].deallocate_count.fetch_add(1, std::memory_order_relaxed);
851 : : }
852 : :
853 : 17 : size_t MemoryPool::get_tier_count() const noexcept { return impl_->tier_count; }
854 : :
855 : 42 : std::vector<MemoryPool::TierStats> MemoryPool::get_stats() const noexcept {
856 : 42 : const size_t count = impl_->tier_count;
857 : :
858 : 42 : std::vector<TierStats> result;
859 : :
860 : : try {
861 [ + - ]: 42 : result.reserve(count);
862 : : } catch (...) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
863 : : return {}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
864 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
865 : :
866 [ + + ]: 340 : for (size_t i = 0; i < count; ++i) {
867 : 298 : const MemoryTierState& state = *impl_->tier_states[i];
868 : 298 : uint64_t hits = 0U;
869 : 298 : uint64_t deallocs = 0U;
870 : :
871 [ + + ]: 2682 : for (const auto& shard : state.shards) {
872 : 2384 : hits += shard.hit_count.load(std::memory_order_relaxed);
873 : 4768 : deallocs += shard.deallocate_count.load(std::memory_order_relaxed);
874 : : }
875 : :
876 : 298 : TierStats item;
877 : 298 : item.max_size = state.max_size;
878 : 298 : item.blocks_per_chunk = state.blocks_per_chunk;
879 : 298 : item.block_size = state.block_size;
880 : 298 : item.hit_count = hits;
881 : 298 : item.deallocate_count = deallocs;
882 [ + - ]: 298 : item.in_use_blocks = (hits >= deallocs) ? (hits - deallocs) : 0U;
883 : 298 : item.upstream_alloc_count = state.upstream_alloc_count.load(std::memory_order_relaxed);
884 : 298 : item.upstream_alloc_bytes = state.upstream_alloc_bytes.load(std::memory_order_relaxed);
885 : 298 : item.chunk_count = state.chunk_count.load(std::memory_order_relaxed);
886 : :
887 : 298 : result.emplace_back(item);
888 : : }
889 : :
890 : 42 : return result;
891 : 42 : }
892 : :
893 : 34 : MemoryPool::OversizedStats MemoryPool::get_oversized_stats() const noexcept {
894 : 34 : OversizedStats result;
895 : :
896 : 34 : result.alloc_count = impl_->oversized_alloc.count.load(std::memory_order_relaxed);
897 : 34 : result.alloc_bytes = impl_->oversized_alloc.bytes.load(std::memory_order_relaxed);
898 : 34 : result.dealloc_count = impl_->oversized_dealloc_count.load(std::memory_order_relaxed);
899 : :
900 : 34 : return result;
901 : : }
902 : :
903 : 1 : void MemoryPool::reset_stats() noexcept {
904 : 1 : const size_t count = impl_->tier_count;
905 : :
906 [ + + ]: 2 : for (size_t i = 0; i < count; ++i) {
907 : 1 : MemoryTierState& state = *impl_->tier_states[i];
908 : :
909 [ + + ]: 9 : for (auto& shard : state.shards) {
910 : 8 : shard.hit_count.store(0, std::memory_order_relaxed);
911 : 8 : shard.deallocate_count.store(0, std::memory_order_relaxed);
912 : : }
913 : : }
914 : :
915 : 1 : impl_->oversized_alloc.count.store(0, std::memory_order_relaxed);
916 : 1 : impl_->oversized_alloc.bytes.store(0, std::memory_order_relaxed);
917 : 1 : impl_->oversized_dealloc_count.store(0, std::memory_order_relaxed);
918 : 1 : }
919 : :
920 : 1314 : void MemoryPool::clear() noexcept {
921 : : static constexpr size_t kStackSlots = 64U;
922 : :
923 [ + + ]: 2660 : for (auto& state : impl_->owned_states) {
924 : 1346 : std::unique_lock grow_lock(state->grow_mtx);
925 : :
926 : 1346 : size_t stack_free_counts[kStackSlots] = {};
927 : 1346 : MemoryChunk stack_to_delete[kStackSlots];
928 : :
929 : 1346 : std::vector<size_t> heap_free_counts;
930 : 1346 : std::vector<MemoryChunk> heap_to_delete;
931 : :
932 : 1346 : const size_t chunks_hint = state->chunk_count.load(std::memory_order_relaxed);
933 : :
934 [ - + ]: 1346 : if VUNLIKELY (chunks_hint > kStackSlots) {
935 : : try {
936 : : // LCOV_EXCL_START GCOVR_EXCL_START
937 : : heap_free_counts.reserve(chunks_hint);
938 : : heap_to_delete.reserve(chunks_hint);
939 : : } catch (std::exception&) {
940 : : }
941 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
942 : : }
943 : :
944 : 1346 : MemoryChunk* to_delete = nullptr;
945 : 1346 : size_t to_delete_count = 0U;
946 : :
947 : : {
948 : 1346 : MemoryTierShardLockGuard shard_locks(*state);
949 : :
950 : 1346 : const size_t chunk_count = state->chunks.size();
951 : :
952 [ + + ]: 1346 : if VUNLIKELY (chunk_count == 0U) {
953 : 466 : continue;
954 : : }
955 : :
956 : 880 : size_t* free_counts = stack_free_counts;
957 : :
958 : 880 : const bool spill_to_heap = (chunk_count > kStackSlots);
959 : :
960 [ - + ]: 880 : if VUNLIKELY (spill_to_heap) {
961 : : try {
962 : : // LCOV_EXCL_START GCOVR_EXCL_START
963 : : heap_free_counts.assign(chunk_count, 0U);
964 : : } catch (std::exception&) {
965 : : continue;
966 : : }
967 : :
968 : : free_counts = heap_free_counts.data();
969 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
970 : : }
971 : :
972 : 880 : const size_t block_size = state->block_size;
973 : :
974 : 880 : std::sort(state->chunks.begin(), state->chunks.end(), [](const MemoryChunk& a, const MemoryChunk& b) noexcept {
975 : 3 : return reinterpret_cast<std::uintptr_t>(a.ptr) < reinterpret_cast<std::uintptr_t>(b.ptr);
976 : : });
977 : :
978 : 674074 : const auto find_chunk_idx = [&chunk_count, &state](std::uintptr_t addr) noexcept -> size_t {
979 : 224682 : size_t lo = 0;
980 : 224682 : size_t hi = chunk_count;
981 : :
982 [ + - ]: 224696 : while (lo < hi) {
983 : 224696 : const size_t mid = lo + (hi - lo) / 2U;
984 : 224696 : const auto cs = reinterpret_cast<std::uintptr_t>(state->chunks[mid].ptr);
985 : 224696 : const auto ce = cs + state->chunks[mid].bytes;
986 : :
987 [ + + ]: 224696 : if (addr < cs) {
988 : 14 : hi = mid;
989 [ - + ]: 224682 : } else if (addr >= ce) {
990 : : lo = mid + 1U; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
991 : : } else {
992 : 224682 : return mid;
993 : : }
994 : : }
995 : :
996 : : return SIZE_MAX; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
997 : 880 : };
998 : :
999 [ + + ]: 7920 : for (const auto& shard : state->shards) {
1000 [ + + ]: 119381 : for (MemoryFreeNode* node = shard.free_list_head; node != nullptr; node = node->next) {
1001 : 112341 : const size_t idx = find_chunk_idx(reinterpret_cast<std::uintptr_t>(node));
1002 : :
1003 [ + - ]: 112341 : if VLIKELY (idx != SIZE_MAX) {
1004 : 112341 : ++free_counts[idx];
1005 : : }
1006 : : }
1007 : : }
1008 : :
1009 [ + + ]: 7920 : for (auto& shard : state->shards) {
1010 : 7040 : MemoryFreeNode* new_head = nullptr;
1011 : 7040 : MemoryFreeNode* current = shard.free_list_head;
1012 : :
1013 [ + + ]: 119381 : while (current != nullptr) {
1014 : 112341 : MemoryFreeNode* next = current->next;
1015 : 112341 : const size_t idx = find_chunk_idx(reinterpret_cast<std::uintptr_t>(current));
1016 : :
1017 : 112341 : bool keep = false;
1018 : :
1019 [ + - ]: 112341 : if VLIKELY (idx != SIZE_MAX) {
1020 : 112341 : const size_t total_blocks = state->chunks[idx].bytes / block_size;
1021 : 112341 : keep = (free_counts[idx] != total_blocks);
1022 : : }
1023 : :
1024 [ + + ]: 112341 : if (keep) {
1025 : 79172 : current->next = new_head;
1026 : 79172 : new_head = current;
1027 : : }
1028 : :
1029 : 112341 : current = next;
1030 : : }
1031 : :
1032 : 7040 : shard.free_list_head = new_head;
1033 : : }
1034 : :
1035 : 880 : size_t released = 0U;
1036 : 880 : size_t write = 0U;
1037 : :
1038 [ + + ]: 1762 : for (size_t read = 0U; read < chunk_count; ++read) {
1039 : 882 : const size_t total_blocks = state->chunks[read].bytes / block_size;
1040 : :
1041 [ + + ]: 882 : if (free_counts[read] == total_blocks) {
1042 [ + - ]: 254 : if VLIKELY (!spill_to_heap) {
1043 : 254 : stack_to_delete[released] = state->chunks[read];
1044 : : } else {
1045 : : try {
1046 : : // LCOV_EXCL_START GCOVR_EXCL_START
1047 : : heap_to_delete.push_back(state->chunks[read]);
1048 : : } catch (std::exception&) {
1049 : : ::operator delete(state->chunks[read].ptr, state->chunks[read].bytes, std::align_val_t{kBlockAlignment});
1050 : : }
1051 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
1052 : : }
1053 : :
1054 : 254 : ++released;
1055 : : } else {
1056 [ - + ]: 628 : if (read != write) {
1057 : : state->chunks[write] = state->chunks[read]; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1058 : : }
1059 : :
1060 : 628 : ++write;
1061 : : }
1062 : : }
1063 : :
1064 [ + + ]: 880 : if VLIKELY (released > 0U) {
1065 : 253 : state->chunks.resize(write);
1066 : 253 : state->chunk_count.fetch_sub(released, std::memory_order_relaxed);
1067 : : }
1068 : :
1069 [ + - ]: 880 : if VLIKELY (!spill_to_heap) {
1070 : 880 : to_delete = stack_to_delete;
1071 : 880 : to_delete_count = released;
1072 : : } else {
1073 : : to_delete = heap_to_delete.data(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1074 : : to_delete_count = heap_to_delete.size(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
1075 : : }
1076 [ + + ]: 1346 : }
1077 : :
1078 : 880 : grow_lock.unlock();
1079 : :
1080 [ + + ]: 1134 : for (size_t i = 0; i < to_delete_count; ++i) {
1081 : 254 : ::operator delete(to_delete[i].ptr, to_delete[i].bytes, std::align_val_t{kBlockAlignment});
1082 : : }
1083 [ + + + + : 2278 : }
+ + ]
1084 : 1314 : }
1085 : :
1086 : 2 : void MemoryPool::trim() noexcept { clear(); }
1087 : :
1088 : 208 : MemoryPool::Config MemoryPool::get_default_config() {
1089 : 200 : static int level = []() noexcept {
1090 : 400 : const std::string env_value = Utils::get_env("VLINK_MEMORY_LEVEL", "3");
1091 : :
1092 : 200 : int parsed = kDefaultMemoryLevel;
1093 : :
1094 : 200 : const char* first = env_value.data();
1095 : 200 : const char* last = first + env_value.size();
1096 : :
1097 : 200 : auto [ptr, ec] = std::from_chars(first, last, parsed);
1098 : :
1099 [ + - - + : 200 : if VUNLIKELY (ec != std::errc() || ptr != last) {
- + ]
1100 : : // LCOV_EXCL_START GCOVR_EXCL_START
1101 : : CLOG_W("MemoryPool: VLINK_MEMORY_LEVEL=\"%s\" is not a valid integer, fallback to %d.", env_value.c_str(),
1102 : : kDefaultMemoryLevel);
1103 : :
1104 : : return kDefaultMemoryLevel;
1105 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
1106 : : }
1107 : :
1108 [ + - - + : 200 : if VUNLIKELY (parsed < kMinMemoryLevel || parsed > kMaxMemoryLevel) {
- + ]
1109 : : // LCOV_EXCL_START GCOVR_EXCL_START
1110 : : CLOG_W("MemoryPool: VLINK_MEMORY_LEVEL=%d out of range [%d, %d], clamped.", parsed, kMinMemoryLevel,
1111 : : kMaxMemoryLevel);
1112 : :
1113 : : return parsed < kMinMemoryLevel ? kMinMemoryLevel : kMaxMemoryLevel;
1114 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
1115 : : }
1116 : :
1117 : 200 : return parsed;
1118 [ + + + - ]: 408 : }();
1119 : :
1120 [ + + + - : 208 : static bool prealloc_env = (Utils::get_env("VLINK_MEMORY_PREALLOC") == "1");
+ - + - -
- ]
1121 : :
1122 : 200 : static size_t batch_size = []() noexcept {
1123 : 400 : const std::string env_value = Utils::get_env("VLINK_MEMORY_BATCH_SIZE", "16");
1124 : 200 : size_t parsed = kDefaultBatchSize;
1125 : :
1126 : 200 : const char* first = env_value.data();
1127 : 200 : const char* last = first + env_value.size();
1128 : 200 : auto [ptr, ec] = std::from_chars(first, last, parsed);
1129 : :
1130 [ + + - + : 200 : if VUNLIKELY (ec != std::errc() || ptr != last || parsed == 0U) {
+ + + + +
+ ]
1131 : : // LCOV_EXCL_START GCOVR_EXCL_START
1132 : : CLOG_W("MemoryPool: VLINK_MEMORY_BATCH_SIZE=\"%s\" is not a positive integer, fallback to %zu.",
1133 : : env_value.c_str(), kDefaultBatchSize);
1134 : : return kDefaultBatchSize;
1135 : : // LCOV_EXCL_STOP GCOVR_EXCL_STOP
1136 : : }
1137 : :
1138 : 198 : return parsed;
1139 [ + + + - ]: 408 : }();
1140 : :
1141 : 208 : Config config = create_memory_config(level, prealloc_env);
1142 : 208 : config.batch_size = batch_size;
1143 : :
1144 : 208 : return config;
1145 : : }
1146 : :
1147 : 11314 : MemoryPool& MemoryPool::global_instance(bool use_env_level) {
1148 : : #if MEMORY_POOL_NEVER_DELETE
1149 : : alignas(MemoryPool) static char buf[sizeof(MemoryPool)];
1150 : :
1151 : : static auto* instance =
1152 : : new (buf) MemoryPool(use_env_level ? get_default_config() : create_memory_config(kDefaultMemoryLevel, false));
1153 : :
1154 : : return *instance;
1155 : : #else
1156 [ + + + - : 11314 : static MemoryPool instance(use_env_level ? get_default_config() : create_memory_config(kDefaultMemoryLevel, false));
+ - + - -
- + - -
- ]
1157 : :
1158 : 11313 : return instance;
1159 : : #endif
1160 : : }
1161 : :
1162 : 90426 : size_t MemoryPool::find_tier(size_t bytes) const noexcept {
1163 : 90426 : const size_t count = impl_->dispatch_count;
1164 : 89081 : const size_t* const sizes = impl_->dispatch_max_sizes;
1165 : :
1166 [ + + ]: 353144 : for (size_t i = 0; i < count; ++i) {
1167 [ + + ]: 353130 : if (bytes <= sizes[i]) {
1168 : 91268 : return i;
1169 : : }
1170 : : }
1171 : :
1172 : 14 : return kMaxTierCount;
1173 : : }
1174 : :
1175 : : } // namespace vlink
|