xrpld
Loading...
Searching...
No Matches
SlabAllocator.h
1// Copyright (c) 2022, Nikolaos D. Bougalis <nikb@bougalis.net>
2
3#pragma once
4
5#include <xrpl/basics/ByteUtilities.h>
6#include <xrpl/beast/type_name.h>
7#include <xrpl/beast/utility/instrumentation.h>
8
9#include <boost/align.hpp>
10#include <boost/container/static_vector.hpp>
11#include <boost/predef.h>
12
13#include <algorithm>
14#include <atomic>
15#include <cstdint>
16#include <cstring>
17#include <mutex>
18#include <vector>
19
20#if BOOST_OS_LINUX
21#include <sys/mman.h>
22#endif
23
24namespace xrpl {
25
26template <typename Type>
28{
29 static_assert(
30 sizeof(Type) >= sizeof(std::uint8_t*),
31 "SlabAllocator: the requested object must be larger than a pointer.");
32
33 static_assert(alignof(Type) == 8 || alignof(Type) == 4);
34
36 struct SlabBlock
37 {
38 // A mutex to protect the freelist for this block:
40
41 // A linked list of appropriately sized free buffers:
42 std::uint8_t* l = nullptr;
43
44 // The next memory block
46
47 // The underlying memory block:
48 std::uint8_t const* const p = nullptr;
49
50 // The extent of the underlying memory block:
52
54 : next(next), p(data), size(size)
55 {
56 // We don't need to grab the mutex here, since we're the only
57 // ones with access at this moment.
58
59 while (data + item <= p + size)
60 {
61 // Use memcpy to avoid unaligned UB
62 // (will optimize to equivalent code)
63 std::memcpy(data, static_cast<void const*>(&l), sizeof(std::uint8_t*));
64 l = data;
65 data += item;
66 }
67 }
68
69 // Calling this destructor will release the allocated memory but
70 // will not properly destroy any objects that are constructed in
71 // the block itself.
72 ~SlabBlock() = default;
73
74 SlabBlock(SlabBlock const& other) = delete;
76 operator=(SlabBlock const& other) = delete;
77
78 SlabBlock(SlabBlock&& other) = delete;
80 operator=(SlabBlock&& other) = delete;
81
83 bool
84 own(std::uint8_t const* pIn) const noexcept
85 {
86 return (pIn >= p) && (pIn < p + size);
87 }
88
90 allocate() noexcept
91 {
92 std::uint8_t* ret = nullptr; // NOLINT(misc-const-correctness)
93
94 {
95 std::scoped_lock const lock(m);
96 ret = l;
97
98 if (ret != nullptr)
99 {
100 // Use memcpy to avoid unaligned UB
101 // (will optimize to equivalent code)
102 std::memcpy(static_cast<void*>(&l), ret, sizeof(std::uint8_t*));
103 }
104 }
105
106 return ret;
107 }
108
118 void
120 {
121 XRPL_ASSERT(own(ptr), "xrpl::SlabAllocator::SlabBlock::deallocate : own input");
122
123 std::scoped_lock const lock(m);
124
125 // Use memcpy to avoid unaligned UB
126 // (will optimize to equivalent code)
127 std::memcpy(ptr, static_cast<void const*>(&l), sizeof(std::uint8_t*));
128 l = ptr;
129 }
130 };
131
132private:
133 // A linked list of slabs
135
136 // The alignment requirements of the item we're allocating:
138
139 // The size of an item, including the extra bytes requested and
140 // any padding needed for alignment purposes:
142
143 // The size of each individual slab:
145
146public:
155 constexpr explicit SlabAllocator(
156 std::size_t extra,
157 std::size_t alloc = 0,
158 std::size_t align = 0)
159 : itemAlignment_((align != 0u) ? align : alignof(Type))
160 , itemSize_(boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
161 , slabSize_(alloc)
162 {
163 XRPL_ASSERT(
164 (itemAlignment_ & (itemAlignment_ - 1)) == 0,
165 "xrpl::SlabAllocator::SlabAllocator : valid alignment");
166 }
167
168 SlabAllocator(SlabAllocator const& other) = delete;
170 operator=(SlabAllocator const& other) = delete;
171
172 SlabAllocator(SlabAllocator&& other) = delete;
174 operator=(SlabAllocator&& other) = delete;
175
176 // FIXME: We can't destroy the memory blocks we've allocated, because
177 // we can't be sure that they are not being used. Cleaning the
178 // shutdown process up could make this possible.
179 ~SlabAllocator() = default;
180
182 [[nodiscard]] constexpr std::size_t
183 size() const noexcept
184 {
185 return itemSize_;
186 }
187
194 allocate() noexcept
195 {
196 auto slab = slabs_.load();
197
198 while (slab != nullptr)
199 {
200 if (auto ret = slab->allocate())
201 return ret;
202
203 slab = slab->next;
204 }
205
206 // No slab can satisfy our request, so we attempt to allocate a new
207 // one here:
208 std::size_t const size = slabSize_;
209
210 // We want to allocate the memory at a 2 MiB boundary, to make it
211 // possible to use hugepage mappings on Linux:
212 auto buf = boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
213 if (buf == nullptr) [[unlikely]]
214 return nullptr;
215
216#if BOOST_OS_LINUX
217 // When allocating large blocks, attempt to leverage Linux's
218 // transparent hugepage support. It is unclear and difficult
219 // to accurately determine if doing this impacts performance
220 // enough to justify using platform-specific tricks.
221 if (size >= megabytes(std::size_t(4)))
222 madvise(buf, size, MADV_HUGEPAGE);
223#endif
224
225 // We need to carve out a bit of memory for the slab header
226 // and then align the rest appropriately:
227 auto slabData =
228 reinterpret_cast<void*>(reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
229 auto slabSize = size - sizeof(SlabBlock);
230
231 // This operation is essentially guaranteed not to fail but
232 // let's be careful anyways.
233 if (boost::alignment::align(itemAlignment_, itemSize_, slabData, slabSize) == nullptr)
234 {
235 boost::alignment::aligned_free(buf);
236 return nullptr;
237 }
238
239 slab = new (buf) SlabBlock(
240 slabs_.load(), reinterpret_cast<std::uint8_t*>(slabData), slabSize, itemSize_);
241
242 // Link the new slab
243 while (!slabs_.compare_exchange_weak(
244 slab->next, slab, std::memory_order_release, std::memory_order_relaxed))
245 {
246 ; // Nothing to do
247 }
248
249 return slab->allocate();
250 }
251
259 bool
261 {
262 XRPL_ASSERT(
263 ptr,
264 "xrpl::SlabAllocator::SlabAllocator::deallocate : non-null "
265 "input");
266
267 for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next)
268 {
269 if (slab->own(ptr))
270 {
271 slab->deallocate(ptr);
272 return true;
273 }
274 }
275
276 return false;
277 }
278};
279
281template <typename Type>
283{
284private:
285 // The list of allocators that belong to this set
286 boost::container::static_vector<SlabAllocator<Type>, 64> allocators_{};
287
289
290public:
292 {
293 friend class SlabAllocatorSet;
294
295 private:
299
300 public:
301 constexpr SlabConfig(
302 std::size_t extra,
303 std::size_t alloc = 0,
304 std::size_t align = alignof(Type))
305 : extra_(extra), alloc_(alloc), align_(align)
306 {
307 }
308 };
309
311 {
312 // Ensure that the specified allocators are sorted from smallest to
313 // largest by size:
314 std::sort(std::begin(cfg), std::end(cfg), [](SlabConfig const& a, SlabConfig const& b) {
315 return a.extra_ < b.extra_;
316 });
317
318 // We should never have two slabs of the same size
320 std::begin(cfg), std::end(cfg), [](SlabConfig const& a, SlabConfig const& b) {
321 return a.extra_ == b.extra_;
322 }) != cfg.end())
323 {
324 throw std::runtime_error(
325 "SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
326 }
327
328 for (auto const& c : cfg)
329 {
330 auto& a = allocators_.emplace_back(c.extra_, c.alloc_, c.align_);
331
332 if (a.size() > maxSize_)
333 maxSize_ = a.size();
334 }
335 }
336
337 SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
339 operator=(SlabAllocatorSet const& other) = delete;
340
343 operator=(SlabAllocatorSet&& other) = delete;
344
345 ~SlabAllocatorSet() = default;
346
356 allocate(std::size_t extra) noexcept
357 {
358 if (auto const size = sizeof(Type) + extra; size <= maxSize_)
359 {
360 for (auto& a : allocators_)
361 {
362 if (a.size() >= size)
363 return a.allocate();
364 }
365 }
366
367 return nullptr;
368 }
369
377 bool
379 {
380 for (auto& a : allocators_)
381 {
382 if (a.deallocate(ptr))
383 return true;
384 }
385
386 return false;
387 }
388};
389
390} // namespace xrpl
T adjacent_find(T... args)
T begin(T... args)
constexpr SlabConfig(std::size_t extra, std::size_t alloc=0, std::size_t align=alignof(Type))
boost::container::static_vector< SlabAllocator< Type >, 64 > allocators_
constexpr SlabAllocatorSet(std::vector< SlabConfig > cfg)
SlabAllocatorSet & operator=(SlabAllocatorSet &&other)=delete
SlabAllocatorSet(SlabAllocatorSet const &other)=delete
SlabAllocatorSet & operator=(SlabAllocatorSet const &other)=delete
bool deallocate(std::uint8_t *ptr) noexcept
Returns the memory block to the allocator.
std::uint8_t * allocate(std::size_t extra) noexcept
Returns a suitably aligned pointer, if one is available.
SlabAllocatorSet(SlabAllocatorSet &&other)=delete
SlabAllocator & operator=(SlabAllocator &&other)=delete
constexpr std::size_t size() const noexcept
Returns the size of the memory block this allocator returns.
constexpr SlabAllocator(std::size_t extra, std::size_t alloc=0, std::size_t align=0)
Constructs a slab allocator able to allocate objects of a fixed size.
std::atomic< SlabBlock * > slabs_
SlabAllocator & operator=(SlabAllocator const &other)=delete
~SlabAllocator()=default
SlabAllocator(SlabAllocator &&other)=delete
std::size_t const itemAlignment_
SlabAllocator(SlabAllocator const &other)=delete
bool deallocate(std::uint8_t *ptr) noexcept
Returns the memory block to the allocator.
std::uint8_t * allocate() noexcept
Returns a suitably aligned pointer, if one is available.
std::size_t const slabSize_
std::size_t const itemSize_
T end(T... args)
T memcpy(T... args)
std::string typeName()
Definition type_name.h:16
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr auto megabytes(T value) noexcept
T sort(T... args)
A block of memory that is owned by a slab allocator.
SlabBlock(SlabBlock const &other)=delete
bool own(std::uint8_t const *pIn) const noexcept
Determines whether the given pointer belongs to this allocator.
std::uint8_t const *const p
SlabBlock(SlabBlock *next, std::uint8_t *data, std::size_t size, std::size_t item)
SlabBlock & operator=(SlabBlock &&other)=delete
std::uint8_t * allocate() noexcept
void deallocate(std::uint8_t *ptr) noexcept
Return an item to this allocator's freelist.
SlabBlock(SlabBlock &&other)=delete
SlabBlock & operator=(SlabBlock const &other)=delete