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