Project
Loading...
Searching...
No Matches
CapacityEstimator.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 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
13
14#include <algorithm>
15#include <cmath>
16#include <limits>
17#include <mutex>
18#include <tuple>
19#include <unordered_map>
20#include <vector>
21
22#include "Framework/Logger.h"
23
24namespace o2::its
25{
26
28 struct Entry {
29 float ratio{0.f};
30 float margin{0.f};
31 size_t maxEmitted{0};
32 uint32_t nSamples{0};
33 uint32_t nLowStreak{0};
34 uint32_t nOverflows{0};
35 };
36
37 explicit Impl(Config config) : cfg{config} {}
38
40 mutable std::mutex mutex;
41 std::unordered_map<KeyType, Entry> entries;
42};
43
45
46CapacityEstimator::CapacityEstimator(Config cfg) : mImpl{std::make_unique<Impl>(cfg)} {}
47
49
51{
52 std::lock_guard lock{mImpl->mutex};
53 mImpl->entries.clear();
54}
55
56size_t CapacityEstimator::capacity(uint64_t key, double scale) const
57{
58 if (!(scale > 0.)) {
59 return 0;
60 }
61 std::lock_guard lock{mImpl->mutex};
62 const auto it = mImpl->entries.find(key);
63 if (it == mImpl->entries.end() || it->second.nSamples == 0) {
64 return mImpl->cfg.floorSlots;
65 }
66 const auto& e = it->second;
67 const double raw = double(e.ratio) * scale * double(e.margin);
68 if (!std::isfinite(raw) || raw < 0.) {
69 return mImpl->cfg.floorSlots;
70 }
71 // A ratio is only meaningful at the scale it was measured at. Learned on a handful of inputs it
72 // can be arbitrarily large, and applying it to a scale orders of magnitude bigger asks for a slab
73 // nobody can allocate. Bound the request by what this site has ever actually emitted: overshooting
74 // burns memory that a bump allocator cannot give back, undershooting only costs one retry.
75 const size_t ceiling = std::max(mImpl->cfg.floorSlots, static_cast<size_t>(double(e.maxEmitted) * double(mImpl->cfg.marginMax)));
76 if (raw >= static_cast<double>(ceiling)) {
77 return ceiling;
78 }
79 return std::max(mImpl->cfg.floorSlots, static_cast<size_t>(std::ceil(raw)));
80}
81
83{
84 std::lock_guard lock{mImpl->mutex};
85 const auto it = mImpl->entries.find(key);
86 if (it == mImpl->entries.end() || it->second.maxEmitted == 0) {
87 return mImpl->cfg.floorSlots;
88 }
89 const auto& e = it->second;
90 const double raw = double(e.maxEmitted) * double(e.margin);
91 if (!std::isfinite(raw) || raw >= static_cast<double>(std::numeric_limits<size_t>::max())) {
92 return std::numeric_limits<size_t>::max();
93 }
94 return std::max(mImpl->cfg.floorSlots, static_cast<size_t>(std::ceil(raw)));
95}
96
97double CapacityEstimator::expected(uint64_t key, double scale) const
98{
99 if (!(scale > 0.)) {
100 return 0.;
101 }
102 std::lock_guard lock{mImpl->mutex};
103 const auto it = mImpl->entries.find(key);
104 if (it == mImpl->entries.end() || it->second.nSamples == 0) {
105 return 0.;
106 }
107 const double raw = double(it->second.ratio) * scale;
108 return std::isfinite(raw) && raw > 0. ? raw : 0.;
109}
110
111void CapacityEstimator::update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
112{
113 if (!(scale > 0.)) {
114 return;
115 }
116 std::lock_guard lock{mImpl->mutex};
117 auto& e = mImpl->entries[key];
118 const auto& cfg = mImpl->cfg;
119
120 const bool firstSample = e.nSamples == 0;
121 if (firstSample) {
122 e.margin = cfg.marginInit;
123 }
124 const auto sample = static_cast<float>(double(emitted) / scale);
125 e.ratio = firstSample ? sample : (cfg.alpha * sample) + ((1.f - cfg.alpha) * e.ratio);
126 e.maxEmitted = std::max(e.maxEmitted, emitted);
127 ++e.nSamples;
128
129 if (memoryLimited) {
130 e.nLowStreak = 0;
131 e.margin = std::max(cfg.marginMin, e.margin * cfg.marginDown);
132 return;
133 }
134 if (overflowed) {
135 ++e.nOverflows;
136 e.nLowStreak = 0;
137 if (!firstSample) {
138 const float shortfall = capacityUsed ? static_cast<float>(double(emitted) / double(capacityUsed)) : cfg.marginUp;
139 e.margin = std::min(cfg.marginMax, e.margin * std::clamp(shortfall * cfg.marginOverflowSlack, 1.02f, cfg.marginUp));
140 }
141 return;
142 }
143 const float util = capacityUsed ? float(double(emitted) / double(capacityUsed)) : 1.f;
144 if (util < cfg.lowWatermark) {
145 if (++e.nLowStreak >= cfg.decayAfter) {
146 e.margin = std::max(cfg.marginMin, e.margin * cfg.marginDown);
147 e.nLowStreak = 0;
148 }
149 } else if (e.nLowStreak > 0) {
150 --e.nLowStreak;
151 }
152}
153
155{
156 std::lock_guard lock{mImpl->mutex};
157 std::vector<KeyType> keys;
158 keys.reserve(mImpl->entries.size());
159 for (const auto& [key, _] : mImpl->entries) {
160 keys.push_back(key);
161 }
162 std::sort(keys.begin(), keys.end(), [](KeyType a, KeyType b) {
163 const auto da = decodeKey(a);
164 const auto db = decodeKey(b);
165 return std::tie(da.site, da.iteration, da.variant, da.slot) <
166 std::tie(db.site, db.iteration, db.variant, db.slot);
167 });
168 if (keys.empty()) {
169 return;
170 }
171 LOGP(info, "Printing CapacityEstimators:");
172 for (const auto key : keys) {
173 const auto& value = mImpl->entries.at(key);
174 const auto decoded = decodeKey(key);
175 LOGP(info, "\tSite:{} | iter:{} | var:({},{}) | slot:{} | ratio:{} | margin:{} | maxEmitted:{} | sam:{} | low:{} | overflows:{}", SlabSiteNames[decoded.site], decoded.iteration, getVariantHigh(decoded.variant), getVariantLow(decoded.variant), decoded.slot, value.ratio, value.margin, value.maxEmitted, value.nSamples, value.nLowStreak, value.nOverflows);
176 }
177}
178
179} // namespace o2::its
Cross-timeframe output-size prediction.
o2::raw::RawFileWriter * raw
StringRef key
static constexpr int getVariantLow(int variant) noexcept
size_t capacity(uint64_t key, double scale) const
void update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
double expected(uint64_t key, double scale) const
size_t peakCapacity(uint64_t key) const
static constexpr Decoded decodeKey(KeyType key) noexcept
static constexpr int getVariantHigh(int variant) noexcept
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
constexpr const char *const SlabSiteNames[SlabSite::NSlabSite]
std::unordered_map< KeyType, Entry > entries