VLink  2.0.0
A high-performance communication middleware
memory_pool.h
浏览该文件的文档.
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 /**
25  * @file memory_pool.h
26  * @brief Size-class tiered memory pool with per-tier free lists and runtime statistics.
27  *
28  * @details
29  * @c MemoryPool dispatches each allocation request to one of a fixed pyramid of size classes.
30  * Every tier owns a singly-linked free list of fixed-size blocks plus a vector of upstream
31  * chunks; the chunk capacity starts small and doubles geometrically until it reaches the
32  * configured @c blocks_per_chunk. Requests larger than the biggest tier (or with an alignment
33  * stricter than @c alignof(std::max_align_t)) bypass the pool and route directly to
34  * @c ::operator @c new / @c ::operator @c delete.
35  *
36  * @par Tier / bucket source
37  *
38  * | Source | When used |
39  * | ------------------------------------- | ------------------------------------------------------ |
40  * | Caller-supplied @c Config | @c MemoryPool(const @c Config&) with non-empty tiers |
41  * | @c get_default_config() (level 0..9) | @c global_instance(true) |
42  * | Built-in level-3 balanced pyramid | Malformed input fallback or @c global_instance(false) |
43  * | Built-in level @c N row | @c MemoryPool(int @c level, @c bool @c prealloc) ctor |
44  *
45  * @par Allocation flow
46  *
47  * @verbatim
48  * allocate(bytes, align)
49  * |
50  * v
51  * +-----------------+ align > kBlockAlignment? +---------------------+
52  * | guard | ----------------------------------> | oversized bypass |
53  * +-----------------+ | ::operator new |
54  * | no +---------------------+
55  * v
56  * +-----------------+ no fit ----> oversized bypass
57  * | find_tier(bs) |
58  * +--------+--------+
59  * | tier hit
60  * v
61  * +-----------------+ free list non-empty -> pop block
62  * | per-tier lock |
63  * +--------+--------+
64  * | free list empty
65  * v
66  * +-----------------+ ::operator new for next chunk
67  * | upstream alloc | carve into N blocks of tier size
68  * +-----------------+
69  * @endverbatim
70  *
71  * @par Cleanup primitives
72  *
73  * | Method | What it releases | Concurrent traffic? |
74  * | ------------------ | --------------------------------------------- | ------------------- |
75  * | @c clear / @c trim | Only fully-free chunks; live blocks preserved | Safe |
76  * | @c ~MemoryPool | Every chunk unconditionally | Caller must quiesce |
77  *
78  * @par Example
79  * @code
80  * auto& pool = vlink::MemoryPool::global_instance();
81  * void* p = pool.allocate(512);
82  * pool.deallocate(p, 512); // sizes MUST match
83  *
84  * pool.trim(); // periodic memory reclaim
85  * @endcode
86  *
87  * @note Public methods are @c noexcept and safe for concurrent use. Per-tier locking means
88  * traffic across different size classes does not contend. @c deallocate requires the
89  * same @p bytes value passed to @c allocate. @c allocate returns @c nullptr on upstream
90  * OOM and never throws.
91  */
92 
93 #pragma once
94 
95 #include <cstddef>
96 #include <cstdint>
97 #include <memory>
98 #include <vector>
99 
100 #include "./macros.h"
101 
102 namespace vlink {
103 
104 /**
105  * @class MemoryPool
106  * @brief Per-tier free-list pool with runtime statistics and oversized passthrough.
107  *
108  * @details
109  * Thread-safe. Each tier owns its own spin lock so @c allocate, @c deallocate and @c clear on
110  * different size classes never contend. Bypass mode -- selected by an empty tier list -- routes
111  * every request through the global allocator without any pooling.
112  */
114  public:
115  /**
116  * @brief Default block alignment for every pooled allocation.
117  *
118  * @details
119  * Equal to @c alignof(std::max_align_t). Stricter alignments bypass the pool and request
120  * memory directly through @c ::operator @c new with @c std::align_val_t.
121  */
122  static constexpr size_t kBlockAlignment = alignof(std::max_align_t);
123 
124  /**
125  * @brief Descriptor for one size class.
126  */
127  struct Tier final {
128  size_t max_size{0}; ///< Inclusive upper bound of the tier in bytes.
129  size_t blocks_per_chunk{0}; ///< Maximum blocks carved from a single upstream chunk.
130  };
131 
132  /**
133  * @brief Constructor configuration grouping the tier list and the preallocation toggle.
134  *
135  * @details
136  * @c prealloc controls whether the constructor immediately fills every tier to its full
137  * @c blocks_per_chunk quota. Default @c false keeps the lazy growth path. Preallocation is
138  * best effort; any tier whose @c ::operator @c new fails stays in lazy state and the
139  * constructor continues.
140  */
141  struct Config final {
142  std::vector<Tier> tiers; ///< Tier descriptors; empty or all-sentinel selects bypass mode.
143  bool prealloc{false}; ///< When @c true, eagerly fill every managed tier to its quota.
144  };
145 
146  /**
147  * @brief Per-tier runtime statistics snapshot.
148  *
149  * @details
150  * Counters use relaxed atomics; @c in_use_blocks and the lifetime upstream fields are best
151  * effort under concurrent traffic, not globally atomic.
152  */
153  struct TierStats final {
154  size_t max_size{0}; ///< Configured @c max_size for this tier.
155  size_t blocks_per_chunk{0}; ///< Configured @c blocks_per_chunk for this tier.
156  size_t block_size{0}; ///< Effective block size after alignment rounding.
157  uint64_t hit_count{0}; ///< Allocations dispatched to this tier (resettable).
158  uint64_t deallocate_count{0}; ///< Deallocations dispatched to this tier (resettable).
159  uint64_t in_use_blocks{0}; ///< Best-effort @c hit_count @c - @c deallocate_count.
160  uint64_t chunk_count{0}; ///< Currently owned chunks; @c clear decrements by released count.
161  uint64_t upstream_alloc_count{0}; ///< Lifetime number of chunks fully installed in this tier.
162  uint64_t upstream_alloc_bytes{0}; ///< Lifetime bytes of those installed chunks.
163  };
164 
165  /**
166  * @brief Statistics for allocations that bypass the tier free lists.
167  *
168  * @details
169  * Captures requests whose size exceeds the largest tier or whose alignment exceeds
170  * @c kBlockAlignment.
171  */
172  struct OversizedStats final {
173  uint64_t alloc_count{0}; ///< Oversized allocations forwarded to the system allocator.
174  uint64_t alloc_bytes{0}; ///< Total bytes of oversized allocations.
175  uint64_t dealloc_count{0}; ///< Oversized deallocations observed.
176  };
177 
178  /**
179  * @brief Constructs an empty pool in bypass mode.
180  *
181  * @details
182  * Equivalent to @c MemoryPool(Config{}): every request hits @c ::operator @c new / @c delete.
183  */
185 
186  /**
187  * @brief Constructs a tiered pool using the built-in pyramid for @p level.
188  *
189  * @details
190  * Out-of-range values are clamped to @c [0, @c 9] and a warning is logged. Level @c 0 yields
191  * bypass mode; the @p prealloc flag is ignored in that case. Level @c 9 fully saturates to
192  * roughly 656 MiB of resident memory.
193  *
194  * @param level Built-in level in @c [0, @c 9].
195  * @param prealloc When @c true, fills every tier to its quota on construction (best effort).
196  * Default: @c false.
197  */
198  explicit MemoryPool(int level, bool prealloc = false);
199 
200  /**
201  * @brief Constructs a tiered pool with an explicit configuration.
202  *
203  * @details
204  * Empty @c config.tiers selects bypass mode. Sentinel entries with @c blocks_per_chunk
205  * @c == @c 0 declare a size ceiling but are stripped at construction; an all-sentinel list is
206  * therefore equivalent to bypass mode. Malformed input (non-monotonic ordering, duplicate
207  * @c max_size, zero @c max_size or @c max_size below the minimum block size) logs an error
208  * and silently falls back to the level-3 default pyramid. @c std::bad_alloc may propagate
209  * from internal vector growth.
210  *
211  * @param config Tier descriptors and preallocation flag.
212  */
213  explicit MemoryPool(const Config& config);
214 
215  /**
216  * @brief Releases every owned chunk unconditionally.
217  *
218  * @warning The caller must guarantee no outstanding pooled block is in use and no other
219  * thread is calling @c allocate, @c deallocate, @c clear or @c trim on this instance.
220  * Use @c clear for a non-destructive trim.
221  */
223 
224  /**
225  * @brief Allocates @p bytes of memory from the appropriate tier.
226  *
227  * @details
228  * Routes to the first tier whose @c max_size is @c >= @p bytes. Requests larger than the
229  * biggest tier or with @p alignment @c > @c kBlockAlignment bypass the pool. @p bytes @c ==
230  * @c 0 routes to the smallest tier and still returns a unique non-null pointer that must be
231  * passed back to @c deallocate with the same @c 0 size. Never throws.
232  *
233  * @param bytes Requested size.
234  * @param alignment Required alignment (power of two). Default: @c kBlockAlignment.
235  * @return Pointer to allocated memory, or @c nullptr on upstream OOM.
236  */
237  [[nodiscard]] void* allocate(size_t bytes, size_t alignment = kBlockAlignment) noexcept;
238 
239  /**
240  * @brief Returns a block previously allocated through @c allocate to the pool.
241  *
242  * @param p Pointer returned by @c allocate. @c nullptr is a no-op.
243  * @param bytes Original size passed to @c allocate; MUST match.
244  * @param alignment Original alignment passed to @c allocate.
245  */
246  void deallocate(void* p, size_t bytes, size_t alignment = kBlockAlignment) noexcept;
247 
248  /**
249  * @brief Returns the number of live managed tiers.
250  *
251  * @details
252  * @c 0 indicates bypass mode. Sentinel entries are stripped at construction so the count
253  * reflects only tiers backed by a live free list.
254  *
255  * @return Tier count after sentinel stripping.
256  */
257  [[nodiscard]] size_t get_tier_count() const noexcept;
258 
259  /**
260  * @brief Returns a snapshot of per-tier statistics.
261  *
262  * @return Vector with one entry per live tier.
263  */
264  [[nodiscard]] std::vector<TierStats> get_stats() const noexcept;
265 
266  /**
267  * @brief Returns a snapshot of the oversized-passthrough statistics.
268  *
269  * @details
270  * Counters are loaded with relaxed ordering and are not a globally atomic snapshot.
271  *
272  * @return Aggregated counters for the oversized path.
273  */
274  [[nodiscard]] OversizedStats get_oversized_stats() const noexcept;
275 
276  /**
277  * @brief Resets per-call statistics counters to zero.
278  *
279  * @details
280  * Clears @c hit_count and @c deallocate_count on every tier and the @c oversized_* counters.
281  * Physical / lifetime state (@c chunk_count, @c upstream_alloc_count, @c upstream_alloc_bytes)
282  * is preserved.
283  */
284  void reset_stats() noexcept;
285 
286  /**
287  * @brief Releases only fully-free chunks; preserves chunks still backing live allocations.
288  *
289  * @details
290  * For each tier the free list is grouped by owning chunk; chunks whose free-node count equals
291  * their block capacity are released, others stay intact. @c chunk_count is decremented by
292  * the number of released chunks. Safe to call concurrently with @c allocate and
293  * @c deallocate. Per-tier work is @c O(C @c log @c C @c + @c F @c log @c C) under the spin
294  * lock.
295  */
296  void clear() noexcept;
297 
298  /**
299  * @brief Alias of @c clear with a name suited to periodic-trim phrasing.
300  *
301  * @details
302  * Behaviour, complexity and thread-safety are identical to @c clear.
303  */
304  void trim() noexcept;
305 
306  /**
307  * @brief Returns the default tier pyramid using @c VLINK_MEMORY_LEVEL / @c VLINK_MEMORY_PREALLOC.
308  *
309  * @details
310  * Each level @c 0..9 maps to a hand-coded row of @c {max_size, @c blocks_per_chunk} pairs.
311  * Level @c 0 is bypass mode. Levels @c 1..9 return 19 entries covering 32 B to 16 MiB; the
312  * 1 MiB / 4 MiB / 8 MiB / 16 MiB ceilings activate at level @c 2 / @c 4 / @c 5 / @c 6 and are
313  * sentinels below those thresholds. Within each level the 32 B head tier carries twice the
314  * @c blocks_per_chunk of the 64 B tier to absorb high-density tiny allocations.
315  *
316  * @c VLINK_MEMORY_PREALLOC controls the @c prealloc flag; only the literal value @c "1"
317  * enables preallocation.
318  *
319  * @return @c Config ready to pass to the constructor.
320  */
321  [[nodiscard]] static Config get_default_config();
322 
323  /**
324  * @brief Returns the process-wide shared @c MemoryPool instance.
325  *
326  * @details
327  * Lazy Meyers singleton. Only the first call decides the configuration; subsequent calls
328  * return the same instance and ignore the argument.
329  *
330  * @warning Whichever value @p use_env_level takes on the first call is baked in for the
331  * rest of the program's lifetime.
332  *
333  * @param use_env_level @c true (default): use @c get_default_config and honour the
334  * environment. @c false: use the built-in level-3 pyramid.
335  * @return Reference to the global pool.
336  */
337  static MemoryPool& global_instance(bool use_env_level = true);
338 
339  private:
340  size_t find_tier(size_t bytes) const noexcept;
341 
342  struct Impl;
343  std::unique_ptr<Impl> impl_;
344 
346 };
347 
348 } // namespace vlink
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VLINK_EXPORT
Definition: macros.h:81
#define VLINK_DISALLOW_COPY_AND_ASSIGN(classname)
Deletes the copy constructor and copy-assignment operator of classname.
Definition: macros.h:174