16#ifndef TRACKINGITSU_INCLUDE_SLABBUMPALLOCATOR_H_
17#define TRACKINGITSU_INCLUDE_SLABBUMPALLOCATOR_H_
24#include <memory_resource>
30#include <oneapi/tbb/blocked_range.h>
31#include <oneapi/tbb/enumerable_thread_specific.h>
32#include <oneapi/tbb/parallel_for.h>
45 bool valid() const noexcept {
return n != 0; }
53 if (mExhausted.load(std::memory_order_relaxed)) {
56 const size_t base = mCursor.fetch_add(mSlab, std::memory_order_relaxed);
57 if (base >= mCapacity) {
58 mExhausted.store(
true, std::memory_order_relaxed);
61 return {.base = base, .n = std::min(mSlab, mCapacity - base)};
64 [[nodiscard]]
size_t capacity() const noexcept {
return mCapacity; }
65 [[nodiscard]]
size_t slab() const noexcept {
return mSlab; }
68 return std::min(mCursor.load(std::memory_order_relaxed), mCapacity);
71 static size_t suggestSlab(
size_t capacity,
int nThreads,
size_t minSlab = 256,
size_t maxSlab = 4096) noexcept
73 const size_t t =
static_cast<size_t>(std::max(1, nThreads));
74 const size_t fairShare = std::max<size_t>(1,
capacity / t);
75 return std::clamp(std::max<size_t>(1,
capacity / (8 * t)),
76 std::min(minSlab, fairShare),
77 std::min(maxSlab, fairShare));
82 assert(mCursor.load(std::memory_order_relaxed) == 0);
84 mExhausted.store(
capacity == 0, std::memory_order_relaxed);
88 std::atomic<size_t> mCursor{0};
89 std::atomic<bool> mExhausted{
false};
108template <
typename T, SlabMode Mode>
111 static constexpr int32_t NoProducer = -1;
136 template <
typename... Args>
140 assert(mProducer != NoProducer);
143 if (mSlot == mSlotEnd && !refill()) {
144 mSpill.emplace_back(std::forward<Args>(args)...);
146 mSpillProducer.push_back(mProducer);
150 mSink->store(mSlot++, mProducer, std::forward<Args>(args)...);
153 [[nodiscard]]
size_t emitted() const noexcept {
return mEmitted; }
154 [[nodiscard]]
size_t spilled() const noexcept {
return mSpill.size(); }
165 const auto r = mSink->mAlloc.
grab();
172 mSlotEnd =
r.base +
r.n;
179 if (mSlot > mRunBegin) {
180 mRuns.push_back(Run{.begin = mRunBegin, .end = mSlot});
190 int32_t mProducer{NoProducer};
191 bool mDrained{
false};
193 bounded_vector<Run> mRuns;
194 bounded_vector<T> mSpill;
195 bounded_vector<int32_t> mSpillProducer;
199 :
SlabSink{cfg, grantedCapacity(cfg.capacity, cfg.nConcurrentSinks, mr), mr} {}
214 s.requested = mRequested;
216 s.memoryLimited = s.capacity < s.requested;
217 for (
const auto&
h : mHandles) {
218 s.emitted +=
h.emitted();
219 s.spilled +=
h.spilled();
221 s.overflowed = s.spilled != 0;
229 assert(dest.get_allocator().resource()->is_equal(*mMR));
234 for (
auto&
h : mHandles) {
236 nRuns +=
h.mRuns.size();
239 for (
const auto&
h : mHandles) {
240 runs.insert(runs.end(),
h.mRuns.begin(),
h.mRuns.end());
242 std::sort(runs.begin(), runs.end(), [](
const Run&
a,
const Run&
b) { return a.begin < b.begin; });
245 size_t outputSize{0};
246 for (
const auto& run : runs) {
247 for (
size_t slot{run.begin}; slot < run.end; ++slot) {
248 if (outputSize != slot) {
249 mStaging[outputSize] = std::move(mStaging[slot]);
255 mStaging.resize(outputSize);
258 for (
auto&
h : mHandles) {
259 dest.insert(dest.end(), std::make_move_iterator(
h.mSpill.begin()), std::make_move_iterator(
h.mSpill.end()));
262 shrinkIfWasteful(dest);
273 lut.assign(nProducers + 1, 0);
275 for (
size_t s = 0; s < wm; ++s) {
276 const int32_t p = mProducerOf[s];
277 if (p != NoProducer) {
281 for (
const auto&
h : mHandles) {
282 for (
const int32_t p :
h.mSpillProducer) {
286 std::inclusive_scan(lut.begin(), lut.end(), lut.begin());
288 bounded_vector<int> cursor(lut.begin(), lut.begin() +
static_cast<ptrdiff_t
>(nProducers), mMR);
289 for (
size_t s = 0; s < wm; ++s) {
290 const int32_t p = mProducerOf[s];
291 mProducerOf[s] = (p != NoProducer) ? cursor[p]++ : -1;
294 const auto total =
static_cast<size_t>(lut.back());
296 for (
auto&
h : mHandles) {
297 for (
size_t i = 0;
i <
h.mSpill.size(); ++
i) {
298 dest[cursor[
h.mSpillProducer[
i]]++] = std::move(
h.mSpill[
i]);
305 T*
const staging = mStaging.data();
306 tbb::parallel_for(tbb::blocked_range<size_t>(0, wm, 4096), [&](
const tbb::blocked_range<size_t>&
r) {
307 for (
size_t s =
r.begin(); s !=
r.end(); ++s) {
308 const int d = mProducerOf[s];
312 dest[d] = std::move(staging[s]);
323 mRequested{cfg.capacity},
324 mAlloc{granted, cfg.slabOverride ? cfg.slabOverride :
SlabBumpAllocator::suggestSlab(granted, cfg.nThreads)},
327 mHandles{[this]() {
return Handle{
this}; }}
330 mStaging.resize(granted);
332 mProducerOf.assign(granted, NoProducer);
334 }
catch (
const std::bad_alloc&) {
335 discardPreallocation();
336 }
catch (
const std::length_error&) {
337 discardPreallocation();
343 const auto* bounded =
dynamic_cast<const BoundedMemoryResource*
>(mr);
344 if (bounded ==
nullptr) {
347 const size_t used = bounded->getUsedMemory();
348 const size_t limit = bounded->getMaxMemory();
349 const size_t remaining = used < limit ? limit - used : 0;
352 const size_t budget = (remaining / 2) /
static_cast<size_t>(std::max(1, nConcurrentSinks));
353 return std::min(requested, budget / BytesPerSlot);
356 static void shrinkIfWasteful(bounded_vector<T>&
v)
358 if (
v.capacity() >
v.size() + (
v.size() / 4)) {
363 void discardPreallocation()
368 mAlloc.resetCapacity(0);
371 template <
typename... Args>
372 void store(
size_t slot, [[maybe_unused]] int32_t producer, Args&&... args)
374 mStaging[slot] =
T(std::forward<Args>(args)...);
376 mProducerOf[slot] = producer;
381 size_t mRequested{0};
382 SlabBumpAllocator mAlloc;
383 bounded_vector<T> mStaging;
384 bounded_vector<int32_t> mProducerOf;
385 tbb::enumerable_thread_specific<Handle> mHandles;
386 bool mFinalized{
false};
Class for time synchronization of RawReader instances.
static size_t suggestSlab(size_t capacity, int nThreads, size_t minSlab=256, size_t maxSlab=4096) noexcept
size_t slab() const noexcept
SlabBumpAllocator(size_t capacity, size_t slab) noexcept
void resetCapacity(size_t capacity) noexcept
size_t capacity() const noexcept
size_t watermark() const noexcept
size_t emitted() const noexcept
void beginProducer(int32_t p) noexcept
size_t spilled() const noexcept
void emplace(Args &&... args)
SlabSink(SlabSink &&)=delete
SlabSink(const SlabSink &)=delete
static constexpr size_t BytesPerSlot
void finalizeGrouped(size_t nProducers, bounded_vector< int > &lut, bounded_vector< T > &dest)
SlabSink & operator=(SlabSink &&)=delete
SlabSink(const Config &cfg, std::pmr::memory_resource *mr)
void finalizeUnordered(bounded_vector< T > &dest)
SlabSinkStats stats() const
std::pmr::memory_resource * memoryResource() const noexcept
SlabSink & operator=(const SlabSink &)=delete
GLboolean GLboolean GLboolean b
GLboolean GLboolean GLboolean GLboolean a
void deepVectorClear(std::vector< T > &vec)
std::pmr::vector< T > bounded_vector
bool valid() const noexcept
size_t requested
slots the caller predicted it would need
bool overflowed
something did not fit into the staging area
bool memoryLimited
the pool granted less than was requested
size_t capacity
slots the memory pool actually granted
size_t capacity
predicted number of slots
size_t slabOverride
0: derive the slab size from the granted capacity
int nConcurrentSinks
sinks that may be alive on the same pool at the same time
int nThreads
workers that will feed this sink