Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
BlockAllocator.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Memory_BlockAllocator_h_
5#define _Stroika_Foundation_Memory_BlockAllocator_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <cstddef>
10
11#include "Stroika/Foundation/Common/Common.h"
13
14/**
15 * \file
16 *
17 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
18 *
19 *
20 * @todo Consider doing GetMem_Util_ code outside of the context of the lock-guard, and if
21 * we get multiple results, just patch them into the linked list. That way in case of
22 * multithreading (when we're paging in freepool) - we'll do less busy waiting. But, this would
23 * be at the cost of possibly allocating too many blocks when two threads both run out of memory
24 * in the same pool at the same time - though that could be undone).
25 *
26 * @todo Compact() method could be smart enough to move items in free pool that are from
27 * mostly used up pools to the head of the free list, and move mostly unused blocks
28 * to the end of the free pool, which over time should probably tend to defragment
29 * the pools.
30 *
31 * @todo BlockAllocator<T> could hugely benefit from some optimistic locking
32 * strategy - like in http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3341.pdf
33 *
34 * Make sure when we experiment with that - we include it here as one of our first
35 * optimization points!
36 *
37 * There is now a C++ semi-standard for this, and working implementations (I haven't tried). But my current
38 * lockfree approach maybe better.
39 *
40 * @todo Maybe use TRAITS on BlockAllocator to define some stuff
41 * about strategies (options) - like how to share pools?
42 *
43 * @todo BlockAllocator<>::Compact ()
44 * o not sure this is useful, or worth the effort, but since
45 * Sterl wrote it - leave it in for a while - til I get clearer experience.
46 *
47 * o BlockAllocationPool_<SIZE>::Compact () algorithm uses vector, which could fail if
48 * we are so fragmented we cannot allocate the large contiguous block needed for the vector<>.
49 * Catch-22. Maybe find a less costly strategy?
50 *
51 * o I make have broken something in Compact() routine when I transcribed it from Sterl's code.
52 * never tested (by me).
53 */
54
55namespace Stroika::Foundation::Memory {
56
57 /**
58 * Low-level tool to allocate and free memory from a fixed size/element pool. Very high performance since
59 * no searching or coalescing ever needed, but at the cost of creating some amount of fragmentation.
60 *
61 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
62 *
63 * This API operates at the level of malloc/free - just allocating fixed sized blocks and freeing them.
64 *
65 * For easiser use, probably the best approach is to @see UseBlockAllocationIfAppropriate
66 *
67 * \note Design Note: alignas / alignemnt of allocated values
68 * http://stroika-bugs.sophists.com/browse/STK-511
69 * We use sizeof(). We always allocate large blocks which (I htink are always) aligned to the largest
70 * alignemnt required by the system, and we that as an array.
71 *
72 * But I think since sizeof(T) is the offset from one element of an array[T] - our allocations will always be aligned
73 * if the first big block is aligned.
74 *
75 * To double/triple check, we have an Ensure in BlockAllocator<T>::Allocate () to assure aligned allocations
76 *
77 * \note
78 * Assuming the compiler is able to inline the mapping from type T, to which sized pool, there is really no cost in adding more. The reason
79 * to have different type T objects share the same BlockAllocationPool_ is to reduce fragmentation.
80 *
81 * \note
82 * To make BlockAllocator more of a plug-replacement for the std-c++ free-pool allocator, neither Allocate nor Deallocate () are cancelation
83 * points.
84 *
85 * Making them cancelation points led to too many subtle bugs were very rarely we would have 'terminate' called due to a memory allocation
86 * from a no-except method.
87 *
88 * \note
89 * As of this release (v2.1b14) - we don't support use of BlockAllocator for arrays. We've never needed it
90 * or used it and it seems more likely a bug than a feature. And its easily worked around by wrapping
91 * your array in a struct. And it would have a slight performance overhead/cost. MAYBE revisit
92 * in the future.
93 *
94 * But also see:
95 * @see https://en.cppreference.com/w/cpp/named_req/Allocator
96 * @see ManuallyBlockAllocated
97 * @see UseBlockAllocationIfAppropriate
98 */
99 template <typename T>
101 public:
102 using value_type = T;
103
104 public:
105 /**
106 */
107 constexpr BlockAllocator () = default;
108 template <typename U>
109 constexpr BlockAllocator (const BlockAllocator<U>&) noexcept;
110
111 public:
112 /**
113 * \pre (n == 1)
114 *
115 * \note - though this can throw due to memory exhaustion, it will not throw Thread::InterupptionException - it is not a cancelation point.
116 */
117 nonvirtual [[nodiscard]] T* allocate (std::size_t n);
118
119 public:
120 /**
121 * \pre (p allocated by BlockAllocator<T>::Allocate ());
122 * \pre (n == 1)
123 * p can be nullptr
124 */
125 nonvirtual void deallocate (T* p, std::size_t n) noexcept;
126
127 public:
128 /**
129 * Return to the free store all deallocated blocks whcih can be returned.
130 *
131 * This takes O (N), where N is the total number of extant operator new allocations for this size.
132 *
133 * It also currently uses a large block of contiguous memory, which could fail.
134 *
135 * But it might sometimes return memory from a particular data structure (fixed element size pool) to
136 * the general free store.
137 *
138 * Also - beware - this locks out other threads (from allocation/deallocation) during execution (which can take a while).
139 *
140 * \note Similar to shrink_to_fit
141 */
142 static void Compact ();
143
144 public:
145 /**
146 */
147 nonvirtual bool operator== (const BlockAllocator& rhs) const;
148
149 private:
150 // ensure return value is >= sizeof (T), and has legit alignment for T
151 static constexpr size_t AdjustSizeForPool_ ();
152 };
153
154}
155
156/*
157 ********************************************************************************
158 ***************************** Implementation Details ***************************
159 ********************************************************************************
160 */
161#include "BlockAllocator.inl"
162
163#endif /*_Stroika_Foundation_Memory_BlockAllocator_h_*/
nonvirtual void deallocate(T *p, std::size_t n) noexcept