Project
Loading...
Searching...
No Matches
testSlabBumpAllocator.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#define BOOST_TEST_MODULE Test SlabBumpAllocator
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15
16#include <boost/test/unit_test.hpp>
17
18#include <algorithm>
19#include <cstdint>
20#include <limits>
21#include <memory_resource>
22#include <new>
23#include <random>
24#include <stdexcept>
25#include <vector>
26
27#include <oneapi/tbb/parallel_for.h>
28#include <oneapi/tbb/task_arena.h>
29
33
34using namespace o2::itsmft::tracking;
35
36namespace
37{
38
39struct Rec {
40 int a{-1};
41 int b{-1};
42 float payload{0.f};
43 Rec() = default;
44 Rec(int aa, int bb, float p) : a{aa}, b{bb}, payload{p} {}
45 bool operator<(const Rec& o) const
46 {
47 if ((a < 0) != (o.a < 0)) {
48 return o.a < 0;
49 }
50 return a != o.a ? a < o.a : b < o.b;
51 }
52 bool operator==(const Rec& o) const { return a == o.a && b == o.b; }
53};
54
55std::ostream& operator<<(std::ostream& os, const Rec& r)
56{
57 return os << "Rec{" << r.a << ',' << r.b << ',' << r.payload << '}';
58}
59
60class StingyResource final : public std::pmr::memory_resource
61{
62 public:
63 explicit StingyResource(size_t maxBytes) : mMax{maxBytes} {}
64
65 private:
66 void* do_allocate(size_t bytes, size_t alignment) final
67 {
68 if (bytes > mMax) {
69 throw std::bad_alloc{};
70 }
71 return std::pmr::new_delete_resource()->allocate(bytes, alignment);
72 }
73 void do_deallocate(void* p, size_t bytes, size_t alignment) final
74 {
75 std::pmr::new_delete_resource()->deallocate(p, bytes, alignment);
76 }
77 bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final { return this == &other; }
78
79 size_t mMax;
80};
81
82template <typename F>
83void runConcurrently(F&& f)
84{
85 tbb::task_arena arena{4};
86 arena.execute(std::forward<F>(f));
87}
88
89template <typename Emit>
90void produce(int i, uint32_t seed, Emit&& emit)
91{
92 std::mt19937 rng(seed + (uint32_t(i) * 2654435761u));
93 const int n = int(rng() % 12);
94 for (int k = 0; k < n; ++k) {
95 emit(i, k, float((i * 100) + k));
96 }
97}
98
99std::vector<std::vector<Rec>> reference(int nProducers, uint32_t seed)
100{
101 std::vector<std::vector<Rec>> out(nProducers);
102 for (int i = 0; i < nProducers; ++i) {
103 produce(i, seed, [&](int a, int b, float p) { out[i].emplace_back(a, b, p); });
104 }
105 return out;
106}
107
108struct EstimatorSnapshot {
109 size_t capacity{0};
110 size_t peakCapacity{0};
111 double expected{0.};
113};
114
115EstimatorSnapshot snapshot(const CapacityEstimator& estimator, CapacityEstimator::KeyType key, double scale)
116{
117 return {.capacity = estimator.capacity(key, scale),
118 .peakCapacity = estimator.peakCapacity(key),
119 .expected = estimator.expected(key, scale),
120 .statistics = estimator.statistics(key)};
121}
122
123void checkSnapshot(const EstimatorSnapshot& actual, const EstimatorSnapshot& expected)
124{
125 BOOST_TEST(actual.capacity == expected.capacity);
126 BOOST_TEST(actual.peakCapacity == expected.peakCapacity);
127 BOOST_TEST(actual.expected == expected.expected);
128 BOOST_TEST(actual.statistics.requested == expected.statistics.requested);
129 BOOST_TEST(actual.statistics.granted == expected.statistics.granted);
130 BOOST_TEST(actual.statistics.emitted == expected.statistics.emitted);
131 BOOST_TEST(actual.statistics.spilled == expected.statistics.spilled);
132 BOOST_TEST(actual.statistics.maxEmitted == expected.statistics.maxEmitted);
133 BOOST_TEST(actual.statistics.samples == expected.statistics.samples);
134 BOOST_TEST(actual.statistics.overflowEvents == expected.statistics.overflowEvents);
135 BOOST_TEST(actual.statistics.nLowStreak == expected.statistics.nLowStreak);
136}
137
138void checkGrouped(int nProducers, size_t capacity, size_t slab, size_t maxMemory = std::numeric_limits<size_t>::max())
139{
140 constexpr uint32_t seed = 7u;
141 BoundedMemoryResource mr{maxMemory};
142
143 const auto ref = reference(nProducers, seed);
144 std::vector<Rec> flat;
145 std::vector<int> refLut(nProducers + 1, 0);
146 for (int i = 0; i < nProducers; ++i) {
147 refLut[i + 1] = refLut[i] + int(ref[i].size());
148 flat.insert(flat.end(), ref[i].begin(), ref[i].end());
149 }
150
151 GroupedSlabSink<Rec> sink{{.capacity = capacity, .nThreads = 4, .slabOverride = slab}, &mr};
152 runConcurrently([&] {
153 tbb::parallel_for(0, nProducers, [&](int i) {
154 auto& h = sink.local();
155 h.beginProducer(i);
156 produce(i, seed, [&](int a, int b, float p) { h.emplace(a, b, p); });
157 });
158 });
159
160 const auto st = sink.stats();
161 BOOST_TEST(st.emitted == flat.size());
162
163 bounded_vector<int> lut{&mr};
164 bounded_vector<Rec> dest{&mr};
165 sink.finalizeGrouped(size_t(nProducers), lut, dest);
166
167 BOOST_REQUIRE(lut.size() == size_t(nProducers) + 1);
168 BOOST_TEST(std::equal(lut.begin(), lut.end(), refLut.begin()));
169 BOOST_REQUIRE(dest.size() == flat.size());
170 for (size_t i = 0; i < flat.size(); ++i) {
171 BOOST_TEST(dest[i] == flat[i]);
172 BOOST_TEST(dest[i].payload == flat[i].payload);
173 }
174}
175
176void checkUnordered(int nProducers, size_t capacity, size_t slab, size_t maxMemory = std::numeric_limits<size_t>::max())
177{
178 constexpr uint32_t seed = 11u;
179 BoundedMemoryResource mr{maxMemory};
180
181 const auto ref = reference(nProducers, seed);
182 std::vector<Rec> flat;
183 for (const auto& v : ref) {
184 flat.insert(flat.end(), v.begin(), v.end());
185 }
186 std::sort(flat.begin(), flat.end());
187 flat.erase(std::unique(flat.begin(), flat.end()), flat.end());
188
189 UnorderedSlabSink<Rec> sink{{.capacity = capacity, .nThreads = 4, .slabOverride = slab}, &mr};
190 runConcurrently([&] {
191 tbb::parallel_for(0, nProducers, [&](int i) {
192 auto& h = sink.local();
193 produce(i, seed, [&](int a, int b, float p) { h.emplace(a, b, p); });
194 });
195 });
196
197 const auto st = sink.stats();
198 BOOST_TEST(st.emitted == flat.size());
199
200 bounded_vector<Rec> dest{&mr};
201 sink.finalizeUnordered(dest);
202
203 std::sort(dest.begin(), dest.end());
204
205 BOOST_REQUIRE(dest.size() == flat.size());
206 for (size_t i = 0; i < flat.size(); ++i) {
207 BOOST_TEST(dest[i] == flat[i]);
208 BOOST_TEST(dest[i].payload == flat[i].payload);
209 }
210}
211
212} // namespace
213
214BOOST_AUTO_TEST_CASE(slab_hands_out_disjoint_ranges)
215{
216 SlabBumpAllocator alloc{1000, 256};
217 std::vector<char> seen(1000, 0);
218 size_t got{0};
219 while (true) {
220 const auto r = alloc.grab();
221 if (!r.valid()) {
222 break;
223 }
224 BOOST_REQUIRE(r.base + r.n <= 1000);
225 for (size_t s = r.base; s < r.base + r.n; ++s) {
226 BOOST_REQUIRE(seen[s] == 0);
227 seen[s] = 1;
228 }
229 got += r.n;
230 }
231 BOOST_TEST(got == 1000u);
232 BOOST_TEST(alloc.watermark() <= 1000u);
233}
234
235BOOST_AUTO_TEST_CASE(slab_never_exceeds_a_threads_fair_share)
236{
239 BOOST_TEST(SlabBumpAllocator::suggestSlab(1u << 20, 8) == 4096u);
240}
241
242BOOST_AUTO_TEST_CASE(grouped_reproduces_two_pass_layout)
243{
244 checkGrouped(2000, 40000, 512);
245 checkGrouped(300, 20000, 4096);
246}
247
248BOOST_AUTO_TEST_CASE(grouped_survives_capacity_underestimate)
249{
250 checkGrouped(2000, 3000, 256);
251 checkGrouped(500, 0, 1, 1u << 20);
252}
253
254BOOST_AUTO_TEST_CASE(grouped_survives_capacity_overestimate)
255{
256 checkGrouped(20, 1u << 20, 256, 1u << 16);
257}
258
259BOOST_AUTO_TEST_CASE(grouped_keeps_order_across_slab_and_spill_boundaries)
260{
262 const std::vector<int> counts{3, 5, 6, 0, 2};
263 GroupedSlabSink<Rec> sink{{.capacity = 10, .nThreads = 1, .slabOverride = 4}, &mr};
264
265 auto& h = sink.local();
266 for (size_t p = 0; p < counts.size(); ++p) {
267 h.beginProducer(int(p));
268 for (int k = 0; k < counts[p]; ++k) {
269 h.emplace(int(p), k, float(k));
270 }
271 }
272 const auto st = sink.stats();
273 BOOST_TEST(st.emitted == 16u);
274 BOOST_TEST(st.spilled == 6u); // capacity 10 of 16
275 BOOST_TEST(st.overflowed);
276
277 bounded_vector<int> lut{&mr};
278 bounded_vector<Rec> dest{&mr};
279 sink.finalizeGrouped(counts.size(), lut, dest);
280
281 BOOST_REQUIRE(lut.size() == counts.size() + 1);
282 BOOST_REQUIRE(dest.size() == 16u);
283 int expected{0};
284 for (size_t p = 0; p < counts.size(); ++p) {
285 BOOST_TEST(lut[p] == expected);
286 for (int k = 0; k < counts[p]; ++k) {
287 BOOST_TEST(dest[expected + k] == Rec(int(p), k, 0.f));
288 }
289 expected += counts[p];
290 }
291 BOOST_TEST(lut.back() == expected);
292}
293
294BOOST_AUTO_TEST_CASE(unordered_reproduces_emitted_records)
295{
296 checkUnordered(2000, 40000, 512);
297 checkUnordered(300, 20000, 4096);
298}
299
300BOOST_AUTO_TEST_CASE(unordered_survives_capacity_underestimate)
301{
302 checkUnordered(2000, 3000, 256);
303 checkUnordered(500, 0, 1, 1u << 20);
304}
305
306BOOST_AUTO_TEST_CASE(unordered_keeps_records_across_slab_and_spill_boundaries)
307{
309 UnorderedSlabSink<Rec> sink{{.capacity = 10, .nThreads = 1, .slabOverride = 4}, &mr};
310
311 auto& h = sink.local();
312 for (int i = 0; i < 14; ++i) {
313 h.emplace(i, i + 1, float(i));
314 }
315 const auto st = sink.stats();
316 BOOST_TEST(st.emitted == 14u);
317 BOOST_TEST(st.spilled == 4u);
318
319 bounded_vector<Rec> dest{&mr};
320 sink.finalizeUnordered(dest);
321
322 BOOST_REQUIRE(dest.size() == 14u);
323 for (int i = 0; i < 14; ++i) {
324 BOOST_TEST(dest[i] == Rec(i, i + 1, float(i)));
325 }
326}
327
328BOOST_AUTO_TEST_CASE(unordered_removes_unused_slots)
329{
331 UnorderedSlabSink<Rec> sink{{.capacity = 10, .nThreads = 1, .slabOverride = 4}, &mr};
332 sink.local().emplace(1, 2, 3.f);
333 sink.local().emplace();
334
335 bounded_vector<Rec> dest{&mr};
336 sink.finalizeUnordered(dest);
337
338 BOOST_REQUIRE(dest.size() == 2u);
339 BOOST_TEST(dest.front() == Rec(1, 2, 3.f));
340 BOOST_TEST(dest.front().payload == 3.f);
341 BOOST_TEST(dest.back() == Rec{});
342}
343
344BOOST_AUTO_TEST_CASE(unordered_does_not_hand_back_an_oversized_buffer)
345{
347 UnorderedSlabSink<Rec> sink{{.capacity = 100000, .nThreads = 1, .slabOverride = 256}, &mr};
348
349 auto& h = sink.local();
350 for (int i = 0; i < 100; ++i) {
351 h.emplace(i, i + 1, float(i));
352 }
353 bounded_vector<Rec> dest{&mr};
354 sink.finalizeUnordered(dest);
355
356 BOOST_REQUIRE(dest.size() == 100u);
357 BOOST_TEST(dest.capacity() < 1000u);
358}
359
360BOOST_AUTO_TEST_CASE(capacity_is_clamped_to_what_the_pool_can_spare)
361{
362 constexpr size_t maxMemory = 1u << 16;
363 BoundedMemoryResource mr{maxMemory};
364 UnorderedSlabSink<Rec> sink{{.capacity = 1u << 20, .nThreads = 4}, &mr};
365
366 const auto st = sink.stats();
367 BOOST_TEST(st.requested == size_t{1u << 20});
368 BOOST_TEST(st.capacity > 0u);
369 BOOST_TEST(st.capacity < st.requested);
370 BOOST_TEST(st.memoryLimited);
371 BOOST_TEST(st.capacity * sizeof(Rec) <= maxMemory / 2);
372}
373
374BOOST_AUTO_TEST_CASE(capacity_is_split_between_concurrent_sinks)
375{
376 size_t alone{0}, shared{0};
377 {
378 BoundedMemoryResource mr{1u << 16};
379 UnorderedSlabSink<Rec> sink{{.capacity = 1u << 20, .nThreads = 4, .nConcurrentSinks = 1}, &mr};
380 alone = sink.stats().capacity;
381 }
382 {
383 BoundedMemoryResource mr{1u << 16};
384 UnorderedSlabSink<Rec> sink{{.capacity = 1u << 20, .nThreads = 4, .nConcurrentSinks = 4}, &mr};
385 shared = sink.stats().capacity;
386 }
387 BOOST_TEST(shared > 0u);
388 BOOST_TEST(shared < alone);
389 BOOST_TEST(shared * 4 <= alone + 8); // integer division slack
390}
391
392BOOST_AUTO_TEST_CASE(unordered_survives_a_failed_preallocation)
393{
394 StingyResource mr{1u << 12};
395 UnorderedSlabSink<Rec> sink{{.capacity = 1u << 20, .nThreads = 1}, &mr};
396
397 const auto st = sink.stats();
398 BOOST_TEST(st.capacity == 0u);
399 BOOST_TEST(st.memoryLimited);
400
401 auto& handle = sink.local();
402 for (int i = 0; i < 10; ++i) {
403 handle.emplace(i, i + 1, float(i));
404 }
405
406 bounded_vector<Rec> dest{&mr};
407 sink.finalizeUnordered(dest);
408 BOOST_REQUIRE(dest.size() == 10u);
409 for (int i = 0; i < 10; ++i) {
410 BOOST_TEST(dest[i] == Rec(i, i + 1, float(i)));
411 }
412}
413
414BOOST_AUTO_TEST_CASE(estimator_cold_start_has_capacity)
415{
417 const auto key = CapacityEstimator::makeKey(SlabSite::Cells, 0, 0, 3);
418 BOOST_TEST(est.capacity(key, 1000.) == 1024u);
419
420 est.update(key, 0., 0, 0, false, false);
421 BOOST_TEST(est.capacity(key, 0.) == 0u);
422 BOOST_TEST(est.capacity(key, 1000.) == 1024u);
423
424 est.update(key, 1000., 0, 1024, false, false);
425 BOOST_TEST(est.capacity(key, 1000.) == 1024u);
426}
427
428BOOST_AUTO_TEST_CASE(estimator_converges_and_reacts_to_overflow)
429{
431 const auto key = CapacityEstimator::makeKey(SlabSite::Tracklets, 1, 0, 0);
432 constexpr double scale = 1000.;
433 constexpr double rate = 5.;
434
435 for (int tf = 0; tf < 12; ++tf) {
436 const size_t cap = est.capacity(key, scale);
437 const auto emitted = size_t(scale * rate);
438 est.update(key, scale, emitted, cap != 0 ? cap : emitted, cap != 0 && emitted > cap, false);
439 }
440
441 const size_t cap = est.capacity(key, scale);
442 BOOST_TEST(cap >= size_t(scale * rate));
443 BOOST_TEST(cap <= size_t(scale * rate * 1.35));
444
445 const size_t bigger = est.capacity(key, 2. * scale);
446 BOOST_TEST(bigger > size_t(2. * scale * rate));
447 BOOST_TEST(bigger <= size_t(2. * scale * rate * 1.35));
448
449 est.update(key, scale, size_t(scale * rate * 4.), size_t(scale * rate), true, false);
450 BOOST_TEST(est.capacity(key, scale) > cap);
451}
452
453BOOST_AUTO_TEST_CASE(estimator_does_not_extrapolate_a_low_statistics_ratio)
454{
455 // A first sample taken on a handful of inputs sets the ratio outright, so without a ceiling the
456 // next timeframe would ask for a slab orders of magnitude past anything the site ever emitted.
458 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 2, CapacityEstimator::makeVariant(3, 3), 5);
459 constexpr size_t emitted = 100000;
460
461 est.update(key, 2., emitted, est.capacity(key, 2.), true, false); // ratio of 50000, from two inputs
462
463 const size_t asked = est.capacity(key, 500000.);
464 BOOST_TEST(asked <= emitted * 4u); // bounded by what this site has ever actually produced
465 BOOST_TEST(asked >= emitted); // but still enough headroom not to force a pointless retry
466}
467
468BOOST_AUTO_TEST_CASE(estimator_reports_a_scale_independent_peak)
469{
470 // Sizing a buffer that has to serve several differently sized runs cannot use capacity(), which
471 // needs the scale of one particular run.
473 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 0, CapacityEstimator::makeVariant(5, 3), 2);
474 BOOST_TEST(est.peakCapacity(key) == 1024u); // cold start falls back to the floor
475
476 est.update(key, 1000., 50000, 60000, false, false);
477 BOOST_TEST(est.peakCapacity(key) >= 50000u);
478
479 est.update(key, 10., 700, 1024, false, false); // a much smaller run must not shrink the peak
480 BOOST_TEST(est.peakCapacity(key) >= 50000u);
481 BOOST_TEST(est.peakCapacity(key) <= 50000u * 4u);
482}
483
484BOOST_AUTO_TEST_CASE(estimator_expected_tracks_the_current_input)
485{
486 // Chaining sites whose input is the previous one's output needs a margin-free prediction that
487 // follows this timeframe, not the largest one ever seen.
489 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 0, CapacityEstimator::makeVariant(5, 4), 4);
490 BOOST_TEST(est.expected(key, 1000.) == 0.); // nothing learned yet
491
492 est.update(key, 1000., 2000, 2600, false, false); // ratio of 2
493 BOOST_TEST(est.expected(key, 1000.) == 2000.);
494 BOOST_TEST(est.expected(key, 250.) == 500.); // a smaller timeframe predicts proportionally less
495 BOOST_TEST(est.expected(key, 0.) == 0.);
496
497 // ... while the all-time peak stays where it was, which is why it cannot size a shared buffer.
498 BOOST_TEST(est.peakCapacity(key) >= 2000u);
499}
500
501BOOST_AUTO_TEST_CASE(estimator_ceiling_follows_real_growth)
502{
504 const auto key = CapacityEstimator::makeKey(SlabSite::Cells, 0, 0, 1);
505 constexpr double scale = 1000.;
506 size_t need = 10000;
507
508 for (int tf = 0; tf < 6; ++tf) {
509 const size_t cap = est.capacity(key, scale);
510 est.update(key, scale, need, cap, need > cap, false);
511 need *= 2;
512 }
513 // Each timeframe doubled the output; the ceiling has to have followed, or every one of them
514 // would have paid for a retry.
515 BOOST_TEST(est.capacity(key, scale) >= need / 2);
516}
517
518BOOST_AUTO_TEST_CASE(estimator_backs_off_when_the_pool_refuses)
519{
521 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 2, 0, 0);
522 constexpr double scale = 1000.;
523 constexpr double rate = 5.;
524 const auto emitted = size_t(scale * rate);
525
526 for (int tf = 0; tf < 12; ++tf) {
527 const size_t cap = est.capacity(key, scale);
528 est.update(key, scale, emitted, cap, emitted > cap, false);
529 }
530 const size_t settled = est.capacity(key, scale);
531
532 for (int tf = 0; tf < 12; ++tf) {
533 est.update(key, scale, emitted, 100, true, true);
534 }
535 BOOST_TEST(est.capacity(key, scale) < settled);
536}
537
538BOOST_AUTO_TEST_CASE(estimator_grows_in_proportion_to_the_miss)
539{
541 constexpr double scale = 1000.;
542 const auto nearMiss = CapacityEstimator::makeKey(SlabSite::Cells, 3, 0, 0);
543 const auto wayOff = CapacityEstimator::makeKey(SlabSite::Cells, 3, 0, 1);
544
545 for (const auto key : {nearMiss, wayOff}) {
546 est.update(key, scale, 2000, 2000, false, false);
547 }
548 const size_t settled = est.capacity(nearMiss, scale);
549
550 est.update(nearMiss, scale, 2000, 1900, true, false); // overran by 5%
551 est.update(wayOff, scale, 2000, 500, true, false); // overran by 4x
552
553 const size_t afterNearMiss = est.capacity(nearMiss, scale);
554 const size_t afterWayOff = est.capacity(wayOff, scale);
555 BOOST_TEST(afterNearMiss > settled);
556 BOOST_TEST(afterNearMiss < afterWayOff);
557 BOOST_TEST(afterNearMiss < size_t(1.25 * double(settled)));
558 BOOST_TEST(afterWayOff > size_t(1.4 * double(settled)));
559}
560
561BOOST_AUTO_TEST_CASE(estimator_recovers_from_a_single_overflow)
562{
564 cfg.decayAfter = 1;
565 CapacityEstimator est{cfg};
566 const auto key = CapacityEstimator::makeKey(SlabSite::Tracklets, 4, 0, 0);
567 constexpr double scale = 1000.;
568
569 est.update(key, scale, 2000, 2000, false, false);
570 est.update(key, scale, 2000, 500, true, false);
571 const size_t inflated = est.capacity(key, scale);
572
573 for (int tf = 0; tf < 30; ++tf) {
574 est.update(key, scale, 2000, 20000, false, false); // 10% utilisation
575 }
576 const size_t recovered = est.capacity(key, scale);
577 BOOST_TEST(recovered < inflated);
578 BOOST_TEST(recovered <= size_t(2. * scale * double(cfg.marginMin)) + 2);
579}
580
581BOOST_AUTO_TEST_CASE(estimator_decay_survives_interleaved_busy_timeframes)
582{
584 cfg.decayAfter = 4;
585 CapacityEstimator est{cfg};
586 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 5, 0, 0);
587 constexpr double scale = 1000.;
588
589 est.update(key, scale, 2000, 2000, false, false);
590 est.update(key, scale, 2000, 500, true, false);
591 const size_t inflated = est.capacity(key, scale);
592
593 for (int tf = 0; tf < 80; ++tf) {
594 const bool quiet = (tf % 4) != 3;
595 est.update(key, scale, 2000, quiet ? 20000 : 2000, false, false);
596 }
597 BOOST_TEST(est.capacity(key, scale) < inflated);
598}
599
600BOOST_AUTO_TEST_CASE(estimator_reset_forgets_inflated_margins)
601{
603 const auto key = CapacityEstimator::makeKey(SlabSite::Cells, 0, 0, 0);
604 constexpr double scale = 1000.;
605
606 for (int tf = 0; tf < 6; ++tf) {
607 est.update(key, scale, size_t(scale * 5.), 10, true, false);
608 }
609 BOOST_TEST(est.capacity(key, scale) > 5000u);
610
611 est.reset();
612 BOOST_TEST(est.capacity(key, scale) == 1024u);
613}
614
615BOOST_AUTO_TEST_CASE(estimator_updates_immediately_and_commit_retains_updates)
616{
618 const auto key = CapacityEstimator::makeKey(SlabSite::Neighbours, 2, 0, 4);
619 est.update(key, 100., 120, 100, 95, 7, true, false);
620 const auto immediate = est.statistics(key);
621 BOOST_TEST(immediate.requested == 120u);
622 BOOST_TEST(immediate.granted == 100u);
623 BOOST_TEST(immediate.emitted == 95u);
624 BOOST_TEST(immediate.spilled == 7u);
625 BOOST_TEST(immediate.maxEmitted == 95u);
626 BOOST_TEST(immediate.samples == 1u);
627 BOOST_TEST(immediate.overflowEvents == 1u);
628 BOOST_TEST(immediate.nLowStreak == 0u);
629
630 est.beginTransaction();
631 est.update(key, 100., 80, 80, 70, 0, false, false);
632 const auto beforeCommit = snapshot(est, key, 100.);
633 est.commitTransaction();
634 checkSnapshot(snapshot(est, key, 100.), beforeCommit);
635}
636
637BOOST_AUTO_TEST_CASE(estimator_rollback_restores_the_first_touch_state_exactly)
638{
640 const auto key = CapacityEstimator::makeKey(SlabSite::Neighbours, 2, 0, 4);
641 constexpr double scale = 100.;
642 est.update(key, scale, 120, 100, 95, 7, true, false);
643 const auto before = snapshot(est, key, scale);
644
645 est.beginTransaction();
646 est.update(key, scale, 8000, 6000, 5500, 500, true, false);
647 est.update(key, scale, 40, 400, 20, 0, false, false);
648 const auto during = snapshot(est, key, scale);
649 BOOST_TEST(during.statistics.samples == before.statistics.samples + 2u);
650 BOOST_TEST(during.statistics.requested == before.statistics.requested + 8040u);
651 BOOST_TEST(during.peakCapacity > before.peakCapacity);
652
654 checkSnapshot(snapshot(est, key, scale), before);
655}
656
657BOOST_AUTO_TEST_CASE(estimator_rollback_removes_a_transaction_created_key)
658{
660 const auto key = CapacityEstimator::makeKey(SlabSite::Cells, 3, 0, 5);
661 constexpr double scale = 50.;
662 const auto absent = snapshot(est, key, scale);
663
664 est.beginTransaction();
665 est.update(key, scale, 90, 80, 75, 4, true, false);
666 BOOST_TEST(est.statistics(key).samples == 1u);
667 BOOST_TEST(est.expected(key, scale) > 0.);
669
670 checkSnapshot(snapshot(est, key, scale), absent);
671}
672
673BOOST_AUTO_TEST_CASE(estimator_nested_transaction_rejection_preserves_the_active_transaction)
674{
676 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 1, 0, 2);
677 constexpr double scale = 100.;
678 est.update(key, scale, 50, 50, 40, 0, false, false);
679 const auto before = snapshot(est, key, scale);
680
681 est.beginTransaction();
682 est.update(key, scale, 200, 180, 160, 5, true, false);
683 const auto beforeRejectedBegin = snapshot(est, key, scale);
684 BOOST_CHECK_THROW(est.beginTransaction(), std::logic_error);
685 checkSnapshot(snapshot(est, key, scale), beforeRejectedBegin);
687 checkSnapshot(snapshot(est, key, scale), before);
688
690 est.commitTransaction();
691}
692
693BOOST_AUTO_TEST_CASE(estimator_reset_clears_active_transaction_and_learning)
694{
696 const auto existing = CapacityEstimator::makeKey(SlabSite::Tracklets, 1, 0, 2);
697 const auto created = CapacityEstimator::makeKey(SlabSite::Tracklets, 1, 0, 3);
698 constexpr double scale = 100.;
699 est.update(existing, scale, 200, 180, 170, 3, true, false);
700 est.beginTransaction();
701 est.update(existing, scale, 300, 250, 240, 5, true, false);
702 est.update(created, scale, 100, 90, 80, 2, true, false);
703
704 est.reset();
705 BOOST_TEST(est.statistics(existing).samples == 0u);
706 BOOST_TEST(est.statistics(created).samples == 0u);
707 BOOST_TEST(est.capacity(existing, scale) == 1024u);
708 BOOST_TEST(est.expected(existing, scale) == 0.);
710 est.update(existing, scale, 60, 60, 50, 0, false, false);
711 est.commitTransaction();
712 BOOST_TEST(est.statistics(existing).samples == 1u);
713}
714
715BOOST_AUTO_TEST_CASE(estimator_keys_separate_the_road_walk_steps)
716{
717 const auto a = CapacityEstimator::makeKey(SlabSite::Roads, 0, CapacityEstimator::makeVariant(6, 4), 1);
718 const auto b = CapacityEstimator::makeKey(SlabSite::Roads, 0, CapacityEstimator::makeVariant(5, 4), 1);
719 const auto c = CapacityEstimator::makeKey(SlabSite::Roads, 0, CapacityEstimator::makeVariant(6, 4), 2);
720 BOOST_TEST(a != b);
721 BOOST_TEST(a != c);
722 BOOST_TEST(b != c);
723}
724
725BOOST_AUTO_TEST_CASE(estimator_keys_separate_stage_iteration_and_site)
726{
727 const auto edge0 = CapacityEstimator::makeKey(SlabSite::Tracklets, 0, 0, 0);
728 const auto edge1 = CapacityEstimator::makeKey(SlabSite::Tracklets, 0, 0, 1);
729 const auto nextIteration = CapacityEstimator::makeKey(SlabSite::Tracklets, 1, 0, 0);
730 const auto path0 = CapacityEstimator::makeKey(SlabSite::Cells, 0, 0, 0);
731 const auto path1 = CapacityEstimator::makeKey(SlabSite::Cells, 0, 0, 1);
732 BOOST_TEST(edge0 != edge1);
733 BOOST_TEST(edge0 != nextIteration);
734 BOOST_TEST(edge0 != path0);
735 BOOST_TEST(path0 != path1);
736}
Cross-timeframe output-size prediction.
std::ostream & operator<<(std::ostream &os, const o2::math_utils::Rotation2Df_t &t)
Definition Cartesian.cxx:57
int32_t i
const int16_t bb
uint32_t c
Definition RawData.h:2
Lock-free slot allocator and single-pass sink.
benchmark::State & st
StringRef key
Class for time synchronization of RawReader instances.
static constexpr int makeVariant(int high, int low) noexcept
static constexpr KeyType makeKey(SlabSite site, int iteration, int variant, int slot) noexcept
Statistics statistics(uint64_t key) const
size_t capacity(uint64_t key, double scale) const
double expected(uint64_t key, double scale) const
void update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
static size_t suggestSlab(size_t capacity, int nThreads, size_t minSlab=256, size_t maxSlab=4096) noexcept
GLdouble n
Definition glcorearb.h:1982
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLenum * rate
Definition glcorearb.h:5735
const GLdouble * v
Definition glcorearb.h:832
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint reference
Definition glcorearb.h:5487
GLboolean r
Definition glcorearb.h:1233
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
GLint ref
Definition glcorearb.h:291
std::pmr::vector< T > bounded_vector
std::unique_ptr< GPUReconstructionTimeframe > tf
size_t capacity
slots the memory pool actually granted
std::map< std::string, ID > expected
bool operator==(const CoarseLocation &a, const CoarseLocation &b)
BOOST_AUTO_TEST_CASE(slab_hands_out_disjoint_ranges)
BOOST_CHECK_NO_THROW(algorithm::merge(target, other))
VectorOfTObjectPtrs other
BOOST_TEST(digits==digitsD, boost::test_tools::per_element())