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 <stdexcept>
19#include <vector>
20
21#if BOOST_OS_LINUX
22#include <sys/mman.h>
23#endif
24
25namespace xrpl {
26
27template <typename Type>
29{
30 static_assert(
31 sizeof(Type) >= sizeof(std::uint8_t*),
32 "SlabAllocator: the requested object must be larger than a pointer.");
33
34 static_assert(alignof(Type) == 8 || alignof(Type) == 4);
35
39 struct SlabBlock
40 {
41 // A mutex to protect the freelist for this block:
43
44 // A linked list of appropriately sized free buffers:
45 std::uint8_t* l = nullptr;
46
47 // The next memory block
49
50 // The underlying memory block:
51 std::uint8_t const* const p = nullptr;
52
53 // The extent of the underlying memory block:
55
57 : next(next), p(data), size(size)
58 {
59 // We don't need to grab the mutex here, since we're the only
60 // ones with access at this moment.
61
62 while (data + item <= p + size)
63 {
64 // Use memcpy to avoid unaligned UB
65 // (will optimize to equivalent code)
66 std::memcpy(data, static_cast<void const*>(&l), sizeof(std::uint8_t*));
67 l = data;
68 data += item;
69 }
70 }
71
72 // Calling this destructor will release the allocated memory but
73 // will not properly destroy any objects that are constructed in
74 // the block itself.
75 ~SlabBlock() = default;
76
77 SlabBlock(SlabBlock const& other) = delete;
79 operator=(SlabBlock const& other) = delete;
80
81 SlabBlock(SlabBlock&& other) = delete;
83 operator=(SlabBlock&& other) = delete;
84
88 bool
89 own(std::uint8_t const* pIn) const noexcept
90 {
91 return (pIn >= p) && (pIn < p + size);
92 }
93
95 allocate() noexcept
96 {
97 std::uint8_t* ret = nullptr; // NOLINT(misc-const-correctness)
98
99 {
100 std::scoped_lock const lock(m);
101 ret = l;
102
103 if (ret != nullptr)
104 {
105 // Use memcpy to avoid unaligned UB
106 // (will optimize to equivalent code)
107 std::memcpy(static_cast<void*>(&l), ret, sizeof(std::uint8_t*));
108 }
109 }
110
111 return ret;
112 }
113
124 void
126 {
127 XRPL_ASSERT(own(ptr), "xrpl::SlabAllocator::SlabBlock::deallocate : own input");
128
129 std::scoped_lock const lock(m);
130
131 // Use memcpy to avoid unaligned UB
132 // (will optimize to equivalent code)
133 std::memcpy(ptr, static_cast<void const*>(&l), sizeof(std::uint8_t*));
134 l = ptr;
135 }
136 };
137
138private:
139 // A linked list of slabs
141
142 // The alignment requirements of the item we're allocating:
144
145 // The size of an item, including the extra bytes requested and
146 // any padding needed for alignment purposes:
148
149 // The size of each individual slab:
151
152public:
162 constexpr explicit SlabAllocator(
163 std::size_t extra,
164 std::size_t alloc = 0,
165 std::size_t align = 0)
166 : itemAlignment_((align != 0u) ? align : alignof(Type))
167 , itemSize_(boost::alignment::align_up(sizeof(Type) + extra, itemAlignment_))
168 , slabSize_(alloc)
169 {
170 XRPL_ASSERT(
171 (itemAlignment_ & (itemAlignment_ - 1)) == 0,
172 "xrpl::SlabAllocator::SlabAllocator : valid alignment");
173 }
174
175 SlabAllocator(SlabAllocator const& other) = delete;
177 operator=(SlabAllocator const& other) = delete;
178
179 SlabAllocator(SlabAllocator&& other) = delete;
181 operator=(SlabAllocator&& other) = delete;
182
183 // FIXME: We can't destroy the memory blocks we've allocated, because
184 // we can't be sure that they are not being used. Cleaning the
185 // shutdown process up could make this possible.
186 ~SlabAllocator() = default;
187
191 [[nodiscard]] constexpr std::size_t
192 size() const noexcept
193 {
194 return itemSize_;
195 }
196
204 allocate() noexcept
205 {
206 auto slab = slabs_.load();
207
208 while (slab != nullptr)
209 {
210 if (auto ret = slab->allocate())
211 return ret;
212
213 slab = slab->next;
214 }
215
216 // No slab can satisfy our request, so we attempt to allocate a new
217 // one here:
218 std::size_t const size = slabSize_;
219
220 // We want to allocate the memory at a 2 MiB boundary, to make it
221 // possible to use hugepage mappings on Linux:
222 auto buf = boost::alignment::aligned_alloc(megabytes(std::size_t(2)), size);
223 if (buf == nullptr) [[unlikely]]
224 return nullptr;
225
226#if BOOST_OS_LINUX
227 // When allocating large blocks, attempt to leverage Linux's
228 // transparent hugepage support. It is unclear and difficult
229 // to accurately determine if doing this impacts performance
230 // enough to justify using platform-specific tricks.
231 if (size >= megabytes(std::size_t(4)))
232 madvise(buf, size, MADV_HUGEPAGE);
233#endif
234
235 // We need to carve out a bit of memory for the slab header
236 // and then align the rest appropriately:
237 auto slabData =
238 reinterpret_cast<void*>(reinterpret_cast<std::uint8_t*>(buf) + sizeof(SlabBlock));
239 auto slabSize = size - sizeof(SlabBlock);
240
241 // This operation is essentially guaranteed not to fail but
242 // let's be careful anyways.
243 if (boost::alignment::align(itemAlignment_, itemSize_, slabData, slabSize) == nullptr)
244 {
245 boost::alignment::aligned_free(buf);
246 return nullptr;
247 }
248
249 slab = new (buf) SlabBlock(
250 slabs_.load(), reinterpret_cast<std::uint8_t*>(slabData), slabSize, itemSize_);
251
252 // Link the new slab
253 while (!slabs_.compare_exchange_weak(
254 slab->next, slab, std::memory_order_release, std::memory_order_relaxed))
255 {
256 ; // Nothing to do
257 }
258
259 return slab->allocate();
260 }
261
270 bool
272 {
273 XRPL_ASSERT(
274 ptr,
275 "xrpl::SlabAllocator::SlabAllocator::deallocate : non-null "
276 "input");
277
278 for (auto slab = slabs_.load(); slab != nullptr; slab = slab->next)
279 {
280 if (slab->own(ptr))
281 {
282 slab->deallocate(ptr);
283 return true;
284 }
285 }
286
287 return false;
288 }
289};
290
294template <typename Type>
296{
297private:
298 // The list of allocators that belong to this set
299 boost::container::static_vector<SlabAllocator<Type>, 64> allocators_{};
300
302
303public:
305 {
306 friend class SlabAllocatorSet;
307
308 private:
312
313 public:
314 constexpr SlabConfig(
315 std::size_t extra,
316 std::size_t alloc = 0,
317 std::size_t align = alignof(Type))
318 : extra_(extra), alloc_(alloc), align_(align)
319 {
320 }
321 };
322
324 {
325 // Ensure that the specified allocators are sorted from smallest to
326 // largest by size:
327 std::sort(std::begin(cfg), std::end(cfg), [](SlabConfig const& a, SlabConfig const& b) {
328 return a.extra_ < b.extra_;
329 });
330
331 // We should never have two slabs of the same size
333 std::begin(cfg), std::end(cfg), [](SlabConfig const& a, SlabConfig const& b) {
334 return a.extra_ == b.extra_;
335 }) != cfg.end())
336 {
337 throw std::runtime_error(
338 "SlabAllocatorSet<" + beast::typeName<Type>() + ">: duplicate slab size");
339 }
340
341 for (auto const& c : cfg)
342 {
343 auto& a = allocators_.emplace_back(c.extra_, c.alloc_, c.align_);
344
345 if (a.size() > maxSize_)
346 maxSize_ = a.size();
347 }
348 }
349
350 SlabAllocatorSet(SlabAllocatorSet const& other) = delete;
352 operator=(SlabAllocatorSet const& other) = delete;
353
356 operator=(SlabAllocatorSet&& other) = delete;
357
358 ~SlabAllocatorSet() = default;
359
370 allocate(std::size_t extra) noexcept
371 {
372 if (auto const size = sizeof(Type) + extra; size <= maxSize_)
373 {
374 for (auto& a : allocators_)
375 {
376 if (a.size() >= size)
377 return a.allocate();
378 }
379 }
380
381 return nullptr;
382 }
383
392 bool
394 {
395 for (auto& a : allocators_)
396 {
397 if (a.deallocate(ptr))
398 return true;
399 }
400
401 return false;
402 }
403};
404
405} // 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