Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
use_count_registry.cpp
Go to the documentation of this file.
1/* Flow-IPC: SHM-jemalloc
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
28#include <flow/util/util.hpp>
29#include <flow/util/stat/stat_set.hpp>
30#include <algorithm>
31#include <cassert>
32
34{
35
36Use_count_registry::Use_count_registry(size_t sz, size_t extra_hdr_sz)
37{
38 /* m_header is cted via its no-arg ctor; as advertised that means all scalars are uninitialized (actually the bits
39 * are logically zero but whatever), optional<> empty, and m_bitmap_words untouched/uninitialized (see below about
40 * that). So let's finish the job of setting it up: */
41
43 m_header.m_extra_hdr_sz = extra_hdr_sz;
44
45 /* This is actually quite important. (See its doc header for reason why we are storing it instead of placing
46 * it into a function... and more.) We can reuse get_min_size(), as it assumes the layout we want. */
48
50
51 /* The following m_header stuff is effectively garbage until .m_meta_obj_sz (currently null) gets set by first
52 * allocate(); at that point we set these to real init values:
53 * m_meta_obj_sz, m_bitmap_words_sz, m_bitmap_words_next_search_idx. */
54
55 // That just leaves big array m_bitmap_words.
56#if 0
57 m_header.m_bitmap_words.assign(0);
58
59 /* @todo[done] The above `.assign(0)` is defensive, probably removable; and the benefit could be lowered RAM use.
60 * Explanation: bipc is wrangling OS SHM facilities as a fairly thin
61 * portability+niceness layer; in POSIX OS at least (probably Windows too but need to confirm if it becomes
62 * relevant; as of this writing we're Linux-only officially) it's: ::shm_open() + ::ftruncate() => the vaddr range
63 * is already zero-filled, and the pages are not yet resident (they use no RAM). Without .assign(0) it should
64 * all still work the same. Removing it (the potential to-do) would drop initial resident RAM by ~S_BITMAP_SZ
65 * bytes; bitmap pages would become resident on-demand as allocate() touches them, ~proportionally to
66 * m_header.m_bitmap_words_sz growing. (So if there are 8 quanta total, we start with 1 quantum: about 0/8
67 * of BITMAP_SZ will be resident to start; then the algorithm is such that the entire quantum has to be filled
68 * before adding quantum 2 -- at that point ~1/8 of BITMAP_SZ is resident. If both quanta get totally used-up,
69 * meaning there are that many live objects, then 2/8 of BITMAP_SZ is resident. Etc.) Bottom line -- this can
70 * at best save BITMAP_SZ actual-RAM; as of this writing 128Ki. The more live objects there are -- up to
71 * USE_COUNTS_CAPACITY of them -- the less is saved from the bitmap area. This appears to be "free" and involves
72 * commenting-out/deleting one line; but verify the bipc create-only path's zero-fill guarantee before
73 * doing so. */
74#endif
75 /* m_header.m_bitmap_words is pre-zero-filled, as is required for our algorithm (all parts of the data area
76 * are available; hence all bits in bitmap = 0).
77 * (We leave the historical explicit .assign(0) and @todo (now to-done) for posterity, as it includes the
78 * justification/reasoning for omitting it; in short there's a RAM-use improvement due to non-resident pages.) */
79
80} // Use_count_registry::Use_count_registry()
81
83{
84 using flow::util::ceil_div;
85 using std::fill_n;
86 using std::find_if;
87
88 /* Size, in words (m_bitmap_words array elements), of a bitmap section corresponding to a quantized subsection
89 * of the main data area (which is S_USE_COUNTS_CAPACITY slots divided by some integer; e.g., by 32). */
90 constexpr size_t BITMAP_QUANTUM_WORDS = S_BITMAP_QUANTUM_SZ / sizeof(bit_word_t);
91 constexpr size_t BITMAP_WORDS_MAX_SZ = decltype(m_header.m_bitmap_words)::static_size;
92
93 const bool is_normal_alloc = bool(m_header.m_meta_obj_sz);
94
95 const auto data_start = reinterpret_cast<uint8_t*>(this) + m_header.m_data_start_minus_this;
96 const auto bitmap_words_start = m_header.m_bitmap_words.begin();
97
98 if (!is_normal_alloc)
99 {
100 // Note: This is not the fast-path.
101
102 m_header.m_meta_obj_sz.emplace(sz); // Mark their header as allocated -- and how big it is (see get_metadata()).
103
104 // Note that (sz == 0) is allowed. The logic below will work (essentially no-op).
105
106 /* sz bytes => ceil(sz / ALLOC_SZ) data-area slots => ceil(sz / ALLOC_SZ) bitmap bits
107 * => ceil(ceil(sz / ALLOC_SZ) / 8) bytes to hold ceil(sz / ALLOC_SZ) bitmap bits. Call this K.
108 * => ceil(K / sizeof(word)) words to hold K bytes. Call that M.
109 * - Set all those bits to 1 (a/k/a set all those words to 0b111111...1).
110 * - Next search will begin at the word right after that (its LSB shall in fact be 0).
111 *
112 * We could set the exact bits instead, but it is annoying and not worth it; just use the entire words.
113 * This will potentially waste a few bits and corresponding N-byte groups in the data area -- which is fine. */
114 m_header.m_bitmap_words_next_search_idx = ceil_div(ceil_div(ceil_div(sz, S_ALLOC_SZ),
115 size_t(8)),
116 sizeof(bit_word_t));
117 assert((m_header.m_bitmap_words_next_search_idx <= BITMAP_QUANTUM_WORDS)
118 && "Metadata area must fit within the 1st quantum; see allocate() doc header.");
119 fill_n(bitmap_words_start,
121 ~(bit_word_t(0)));
122
123 /* Searches will keep going around just the bitmap section [0, BITMAP_QUANTUM_WORDS) words.
124 * (When/if those run out, the range will expand to [0, 2 x BITMAP_QUANTUM_WORDS).
125 * When/if those run out, [0, 3 x ...]... etc., until m_bitmap_words_sz == m_bitmap_words.size().
126 * Then allocation shall fail.) */
127 m_header.m_bitmap_words_sz = BITMAP_QUANTUM_WORDS;
129 += (m_header.m_bitmap_words_next_search_idx // We actually used up this rounded amount, not `sz`.
130 * sizeof(bit_word_t) * 8 * S_ALLOC_SZ);
131 return data_start;
132 }
133 // else if (is_normal_alloc):
134
135 if (sz != S_ALLOC_SZ)
136 {
137 return nullptr; // Be forgiving as promised. @todo Maybe should assert() instead?
138 }
139 // else if (sz == S_ALLOC_SZ):
140
141 // This is the fast-path. Needless to say performance is of the essence.
142
143 /* Strategy is:
144 * - m_bitmap_words_next_search_idx is index of word where to begin search (generally we move it forward to
145 * resume search ~just past where the last one succeeded, wrapping around -- until no more available bits left,
146 * then expand the "playing field" until no more quanta left; then allocate() returns null).
147 * - Search 1 is from there until the end of the "playing field"; if no 0 bit found anywhere there, wrap around
148 * and do search 2 from start of "playing field" through just ahead of where search 1 started. So each
149 * search (search 1 and search 2) is a find_if() through the words in that section of the bitmap.
150 * - Each find_if() does a bit search through a single word (uint64_t as of this writing) using an optimized
151 * intrinsic suited for this. The intrinsic finds the LSB that is 0.
152 * The <playing field> looks like:
153 * <[BITMAP_QUANTUM_WORDS words][BITMAP_QUANTUM_WORDS words]>[BITMAP_QUANTUM_WORDS words]...
154 * In that example at some point we ran out of the 1st quantum and had expanded the <field> from
155 * just the first quantum (the initial setup) to the 1st 2 quanta. So keep doing that as needed, until
156 * all the quanta are used up.
157 *
158 * Regarding find_if(): At least in gcc-9 STL, there's a random-accessor-iterator loop-unroll specialization
159 * of find_if(), so this can actually help perf in this search, versus a manual `for` and the like. */
160
161 unsigned int zero_bit_idx{}; // Initialize to avoid (overzealous) compiler warning in some cases.
162 const auto search_start = bitmap_words_start + m_header.m_bitmap_words_next_search_idx;
163 auto search_end = bitmap_words_start + m_header.m_bitmap_words_sz;
164 auto bitmap_words_addr = find_if(search_start, search_end,
165 [&](bit_word_t bits)
166 {
167 static_assert(sizeof(bit_word_t) == sizeof(long long),
168 "We assume that `long long` is 64-bits, as our stuff is 64-bits, and we use __builtin_ffsll()");
169 return (zero_bit_idx = static_cast<unsigned int>(::__builtin_ffsll(static_cast<int64_t>(~bits)))) != 0;
170 });
171 if (zero_bit_idx == 0)
172 {
173 assert((bitmap_words_addr == search_end) && "Bug in above search?!");
174
175 search_end = bitmap_words_start + m_header.m_bitmap_words_next_search_idx;
176 bitmap_words_addr = find_if(bitmap_words_start, search_start,
177 [&](bit_word_t bits)
178 {
179 return (zero_bit_idx = static_cast<unsigned int>(::__builtin_ffsll(static_cast<int64_t>(~bits)))) != 0;
180 });
181 if (zero_bit_idx == 0)
182 {
183 assert((bitmap_words_addr == search_start) && "Bug in above search?!");
184
185 if (m_header.m_bitmap_words_sz == BITMAP_WORDS_MAX_SZ)
186 {
187 return nullptr; // Ran out! allocate() failed.
188 }
189 // else
190
191 // We shall add a new chunk to playing field... all unallocated... hence 1st bit of 1st word = result of search.
193 zero_bit_idx = 1;
194
195 // Mark the wider playing field.
196 m_header.m_bitmap_words_sz += BITMAP_QUANTUM_WORDS;
197 assert((m_header.m_bitmap_words_sz <= BITMAP_WORDS_MAX_SZ)
198 && "Did we not static_assert() that m_bitmap_words is evenly divided?");
199
200 // ...and fall through.
201 }
202 // else if (zero_bit_idx != 0) [search 2]: fall through.
203 } // else if (zero_bit_idx != 0) [search 1]: fall through.
204
205 /* Search succeeded, but let's get the actual result "coordinates": which word `bitmap_words_idx` and which
206 * (1-based) bit `zero_bit_idx` it is. Err, we have the latter already (just remember it is 1-based). */
207 const auto bitmap_words_idx = bitmap_words_addr - bitmap_words_start;
208
209 // In fact mark it in the bitmap as now taken (turn the 0 into 1).
210 const auto mask = bit_word_t(1) << (zero_bit_idx - 1); // (Careful; z_b_i==1 => bit 0, 2 => bit 1, etc.)
211 assert(((*bitmap_words_addr & mask) == bit_word_t(0)) && "Did we mess up the above logic/use of intrinsic?");
212
213 *bitmap_words_addr |= mask;
214
215 // Set where to start the next search: same word (we hope the next LSB shall be 0)...
216 m_header.m_bitmap_words_next_search_idx = bitmap_words_idx;
217 // ...except that if, like, the LSB with the 0 (now 1!) is bit 64 of 64...
218 if (zero_bit_idx == (sizeof(bit_word_t) * 8))
219 {
220 // ...then begin next search not in that word (since, at least right now, it is all 1s) but in the next word, and...
222 % m_header.m_bitmap_words_sz; // ...wrap around, if we're on last word.
223 }
224
225 // Mark stats.
227
228 // Last but not least return the address of N-byte slot that all our bit-searching has enabled us to pinpoint.
229 const auto addr = data_start
230 + (((bitmap_words_idx * sizeof(bit_word_t) * 8) + zero_bit_idx - 1)
231 * S_ALLOC_SZ);
232 assert(flow::util::in_closed_range(data_start, addr, data_start + ((S_USE_COUNTS_CAPACITY - 1) * S_ALLOC_SZ))
233 && "alloc() returning invalid addr: out of range.");
234 return addr;
235} // Use_count_registry::allocate()
236
237void Use_count_registry::deallocate(void* void_addr) noexcept
238{
239 const auto addr = reinterpret_cast<uint8_t*>(void_addr);
240 if (!addr)
241 {
242 return;
243 }
244 // else
245
246 const auto data_start = reinterpret_cast<uint8_t*>(this) + m_header.m_data_start_minus_this;
247
248 if (addr == data_start) // The first (metadata) allocate() returned this.
249 {
250 return; // By contract we allow this (no abort/throw/etc.) but treat it as no-op. Rationale shown elsewhere.
251 }
252 // else
253
254 // Essentially this is the reverse of the last line of allocate().
255
256 assert((addr >= data_start) && "Invalid dealloc(addr): addr out of range (too low).");
257 auto offset_from_data_start = static_cast<size_t>(addr - data_start);
258
259 // offset_from_data_start right now is in bytes.
260 assert(((offset_from_data_start % S_ALLOC_SZ) == 0)
261 && "Invalid dealloc(addr): addr offset from data_start is not a multiple of ALLOC_SZ.");
262 offset_from_data_start /= S_ALLOC_SZ;
263
264 // offset_from_data_start right now is in slots. (In the bitmap, it is 1 bit per slot).
265 assert((offset_from_data_start < S_USE_COUNTS_CAPACITY) && "Invalid dealloc(addr): addr out of range (too high).");
266
267 constexpr size_t BITS_PER_WORD = sizeof(bit_word_t) * 8;
268 m_header.m_bitmap_words[offset_from_data_start / BITS_PER_WORD]
269 &= (~(bit_word_t(1) << (offset_from_data_start % BITS_PER_WORD)));
270
271 m_header.m_allocated_sz -= S_ALLOC_SZ;
272} // Use_count_registry::deallocate()
273
275{
276 /* Take a look at allocate() + our doc header; it should make the following clear.
277 * The K from our doc header -- the compile-time constant by which the # of quanta (1, 2, ...) is multiplied
278 * to get m_bitmap_words_sz -- is allocate()'s BITMAP_QUANTUM_WORDS. */
279 return m_header.m_meta_obj_sz ? m_header.m_bitmap_words_sz // Starts at 1xK; then 2xK, 3xK, etc.
280 : 0; // No allocate()s yet: pool was just created, not yet used.
281}
282
283size_t Use_count_registry::stats_record(size_t prev_quanta_active, Obj_db_aux_pool_stats* target_stats)
284{
285 using flow::util::stat::fetch_add;
286 using flow::util::stat::update_hi_wmark;
287
288 // Look at stat_quanta_active() and our doc headers; will introduce the algorithm.
289
290 const auto quanta_active_x_bitmap_qtm_words = stat_quanta_active();
291 if (quanta_active_x_bitmap_qtm_words == prev_quanta_active)
292 {
293 return quanta_active_x_bitmap_qtm_words; // Fast-path: No-op. Nothing has changed since *target_stats was set.
294 }
295 assert((quanta_active_x_bitmap_qtm_words > prev_quanta_active)
296 && "It cannot change and go down; see allocate() et al.");
297 /* else: Let's do it. Important to understand Obj_db_aux_pool_stats docs from here on.
298 *
299 * Slow-path, so we don't *need* to over-worry about saving every cycle; might as well though. */
300
301 /* The divisor equals BITMAP_QUANTUM_WORDS from allocate(). Some code reuse might be nice -- maybe @todo --
302 * but even without it this follows from the definition of m_bitmap_words_sz (stat_quanta_active()). */
303 const auto quanta_added = static_cast<uint64_t>((quanta_active_x_bitmap_qtm_words - prev_quanta_active)
304 / (S_BITMAP_QUANTUM_SZ / sizeof(bit_word_t)));
305
306 /* INC_PER_QTM_SZ is ~how much memory a fully resident (ever-touched) quantum takes:
307 * the data area + the bitmap area where each bit <=> ALLOC_SZ bytes in data area.
308 * Regarding the bitmap part: we no longer do the m_bitmap_words.assign(0) in our ctor (as it's pre-filled
309 * to zeroes anyway), so at least roughly speaking the bitmap area is made resident in the same proportions
310 * as the data area. */
311 constexpr auto INC_PER_QTM_SZ = (S_USE_COUNTS_CAPACITY_QUANTUM_SZ * S_ALLOC_SZ)
313 auto inc_sz = quanta_added * INC_PER_QTM_SZ;
314
315 update_hi_wmark(&target_stats->m_use_ct_active_quanta_hi_wmark,
316 fetch_add(&target_stats->m_use_ct_active_quanta, quanta_added) + quanta_added);
317 if (prev_quanta_active == 0)
318 {
319 /* As promised we consider the pool as non-existent until an allocate() happens; as in practice the pool
320 * is created when and only when the first use-count is required; and the creation is immediately therefore
321 * followed by allocate(sizeof(Metadata)) -- and for that matter the first allocate(sizeof(<use-count>)).
322 * Regardless of that latter "for that matter," this means they must call stat_quanta_active() before
323 * that first allocate() (returns 0) and then after the first or second allocate(); so going from 0 is
324 * when to ++m_aux_pool_count (and at no other time). There isn't really a "setup" time period when
325 * there's a new ++m_aux_pool_count-contributing guy but no ++m_use_ct_active_quanta being contributed by that same
326 * guy; they each contribute at the same time initially. */
327 update_hi_wmark(&target_stats->m_aux_pool_count_hi_wmark,
328 fetch_add(&target_stats->m_aux_pool_count, 1) + 1);
329
330 /* By the same logic, when going from 0 to 1+ qta is when to count the resident RAM taken by all the header
331 * stuff. After that, it's already counted. Now let's be careful here:
332 * - m_data_start_minus_this = size of entire, full pool relative to &m_header (== this), minus the
333 * entire, full data area. What is between &m_header and data-area? Answer: definitely-used stuff
334 * that we should totally count as resident... including `sizeof(m_header) == sizeof(Header)` in fact...
335 * - ...except that not all of `Header m_header` is resident! By our model all of it is, except
336 * that there is a potential hole in m_header.m_bitmap_words; the unused quanta's part of the bitmap.
337 * We already counted the *used* part in inc_sz above. Therefore subtract it from this addition
338 * to inc_sz; the addition is to exclude the quanta.
339 * - There's also the little pre-`*this` header that technically also takes memory, base_offset().
340 * Including it is pedantic as hell (especially given that we're not, like, ultra-precise about which
341 * actual pages are actually resident -- among other things we assume the entire quantum is paged-in
342 * from the jump; not true). And yet! */
344
345 /* These two GAUGEs are to include the max possible values for m_use_ct_active_quanta and m_resident_sz
346 * respectively; meaning one can see "total of m_use_ct_active_quanta of m_use_ct_quanta in-use" and
347 * "total of m_resident_sz of m_mapped_sz mapped RAM are current resident." So, like m_aux_pool_count,
348 * the entire contribution happens one time, when #-quanta goes from 0 to 1+.
349 *
350 * The m_use_ct_quanta value is not strictly necessary, as it could be computed from m_aux_pool_count:
351 * multiply that by # of quanta, hence by MAX_QUANTA below; but we've decided to track it as-if the # of max quanta
352 * per pool is not a compile-time decision; the perf impact should be negligible due to relative rarity of
353 * this code executing. */
354 constexpr auto MAX_QUANTA = S_USE_COUNTS_CAPACITY / S_USE_COUNTS_CAPACITY_QUANTUM_SZ;
355 const auto mapped_sz = get_min_size(m_header.m_extra_hdr_sz) + base_offset();
356
357 static_assert(MAX_QUANTA == (S_BITMAP_SZ / S_BITMAP_QUANTUM_SZ), "Something is off somewhere....");
358 assert((mapped_sz == (((MAX_QUANTA - quanta_added) * INC_PER_QTM_SZ) + inc_sz))
359 && "Something is off somewhere... get_min_size()+base_offset() should = all hdr+all quanta.");
360
361 update_hi_wmark(&target_stats->m_use_ct_quanta_hi_wmark,
362 fetch_add(&target_stats->m_use_ct_quanta, MAX_QUANTA) + MAX_QUANTA);
363 update_hi_wmark(&target_stats->m_mapped_sz_hi_wmark,
364 fetch_add(&target_stats->m_mapped_sz, mapped_sz) + mapped_sz);
365 } // if (prev_quanta_active == 0)
366 update_hi_wmark(&target_stats->m_resident_sz_hi_wmark,
367 fetch_add(&target_stats->m_resident_sz, inc_sz) + inc_sz);
368
369 return quanta_active_x_bitmap_qtm_words;
370} // Use_count_registry::stats_record()
371
373{
374 using flow::util::stat::fetch_sub;
375
376 /* See stats_record(). That should make the below reasonably clear.
377 * @todo A bit of code reuse w/r/t stats_record() w/r/t some of this math wouldn't hurt. */
378
379 const auto quanta_active_x_bitmap_qtm_words = stat_quanta_active();
380
381 if (quanta_active_x_bitmap_qtm_words == 0)
382 {
383 return 0; // Apparently they've never allocate()d, so nothing was counted.
384 }
385 // else
386 const auto quanta_active = quanta_active_x_bitmap_qtm_words / (S_BITMAP_QUANTUM_SZ / sizeof(bit_word_t));
387 constexpr auto MAX_QUANTA = S_USE_COUNTS_CAPACITY / S_USE_COUNTS_CAPACITY_QUANTUM_SZ;
388
389 const auto mapped_sz = get_min_size(m_header.m_extra_hdr_sz) + base_offset();
390
391 fetch_sub(&target_stats->m_aux_pool_count, 1);
392 fetch_sub(&target_stats->m_use_ct_quanta, MAX_QUANTA);
393 fetch_sub(&target_stats->m_mapped_sz, mapped_sz);
394 fetch_sub(&target_stats->m_use_ct_active_quanta, quanta_active);
395 fetch_sub(&target_stats->m_resident_sz,
396 // resident_sz should be (mapped_sz - (<unused quanta> x <1 quantum's contribution>)).
397 mapped_sz - ((MAX_QUANTA - quanta_active)
399
400 return quanta_active_x_bitmap_qtm_words;
401} // Use_count_registry::stats_record_at_deletion()
402
404{
405 // As advertised: no-op (rationale shown elsewhere).
406}
407
409{
410 return m_header.m_pool_sz;
411}
412
414{
416}
417
419{
420 /* Nothing. As advertised, most importantly, m_bitmap_words was not initialized (actively zero-filled).
421 * Use_count_registry ctor will do the rest. */
422}
423
424} // namespace ipc::shm::arena_lend::detail
static constexpr size_t get_min_size(size_t extra_hdr_sz)
Size we require at max capacity (all S_USE_COUNTS_CAPACITY times S_ALLOC_SZ bytes used) for the conta...
size_t get_free_memory() const
How many allocate(N) calls, times S_ALLOC_SZ, would succeed now without deallocate() in-between,...
void grow(size_t extra_sz)
No-op: as of this writing a *this will allocate up to S_USE_COUNTS_CAPACITY times S_ALLOC_SZ,...
static constexpr size_t base_offset()
The number of bytes between the start of the SHM-pool (segment) storing us and us (*this).
static constexpr size_t S_BITMAP_QUANTUM_SZ
The size of each bitmap section (in bytes) corresponding to 1 quantum of use-count slots (each N byte...
static constexpr size_t S_USE_COUNTS_CAPACITY_QUANTUM_SZ
The size of each quantum into a whole number of which the data area (sized S_USE_COUNTS_CAPACITY N-by...
static constexpr size_t S_ALLOC_SZ
This important constant defines the mandatory size of each allocation – that is argument to allocate(...
void deallocate(void *addr) noexcept
Reverses an earlier allocate() call.
size_t stats_record_at_deletion(Obj_db_aux_pool_stats *target_stats)
Assuming one has followed the required algorithm to update *target_stats so far, this method (intende...
size_t stats_record(size_t prev_quanta_active, Obj_db_aux_pool_stats *target_stats)
Updates the various GAUGEs and HI_WMARKs in *target_stats, if and only if it is appropriate to do so ...
uint64_t bit_word_t
In the bitmap mapping the data area of size S_USE_COUNTS_CAPACITY, this defines a word of bits; a wor...
static constexpr size_t S_BITMAP_SZ
The total size of the bitmap area which uses 1 bit per 1 use-count slot (each N bytes wide) of the da...
void * allocate(size_t sz)
Allocates space out of S_USE_COUNTS_CAPACITY times S_ALLOC_SZ bytes available originally,...
size_t stat_quanta_active() const
Returns a non-negative number that is some compile-time constant K times the number of quanta current...
Use_count_registry(size_t sz, size_t extra_hdr_sz)
Constructs a *this, assumed to be in a SHM-pool (segment) at the time the Sparse_managed_shm is being...
static constexpr size_t S_USE_COUNTS_CAPACITY
Compile-time knob: how many (N-byte) use-counts one *this can allocate.
size_t get_size() const
Returns the value passed to ctor, when the pool was created.
Segregated private stuff for ipc::shm::arena_lend.
Header()
Ctor that leaves all scalars uninitialized, optionals empty, and m_bitmap_words untouched (therefore ...
size_t m_data_start_minus_this
For speed, precomputed/memorized offset from this (a/k/a the start of the Header) where the data area...
size_t m_extra_hdr_sz
See ctor and/or get_min_size(). Essentially const after being assigned.
size_t m_bitmap_words_next_search_idx
In the range [0, m_bitmap_words_sz), this is the index into m_bitmap_words where the next search (in ...
Bitmap_words m_bitmap_words
The bitmap, where 0 represents a free slot; 1 a taken slot.
std::optional< size_t > m_meta_obj_sz
The size passed to the first (metadata) allocate(); or null until then.
size_t m_pool_sz
See get_size(). Essentially const after being assigned.
size_t m_allocated_sz
How much of the data area is in use, in bytes; i.e., the count of 1-bits in m_bitmap_words,...
size_t m_bitmap_words_sz
At all times a positive multiple of S_BITMAP_QUANTUM_SZ / sizeof(bit_word_t), and at most S_USE_COUNT...
Stats about the RAM use of the arena-lending SHM-provider's auxiliary (non-user-data) SHM-pools which...
std::atomic< uint64_t > m_aux_pool_count
Number of aux SHM-pools currently in existence; incremented when a given owner-process thread first e...
std::atomic< uint64_t > m_aux_pool_count_hi_wmark
Max of m_aux_pool_count observed so far.
std::atomic< uint64_t > m_use_ct_quanta_hi_wmark
Max of m_use_ct_quanta observed so far.
std::atomic< uint64_t > m_use_ct_active_quanta_hi_wmark
Max of m_use_ct_active_quanta observed so far.
std::atomic< size_t > m_resident_sz
Of the mapped space tracked (in bytes) in m_mapped_sz, the approximate/ceiling size of resident RAM (...
std::atomic< size_t > m_mapped_sz
Total vaddr space, in bytes, reserved for SHM aux-pools.
std::atomic< uint64_t > m_use_ct_active_quanta
Of the quanta counted in m_use_ct_quanta, count of the ones in active use.
std::atomic< size_t > m_resident_sz_hi_wmark
Max of m_resident_sz observed so far.
std::atomic< size_t > m_mapped_sz_hi_wmark
Max of m_mapped_sz observed so far.
std::atomic< uint64_t > m_use_ct_quanta
Count of quanta available – not necessarily (or typically) all active – across the currently live aux...