Project
Loading...
Searching...
No Matches
BoundedAllocator.h
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.
15
16#ifndef TRACKINGITSU_INCLUDE_BOUNDEDALLOCATOR_H_
17#define TRACKINGITSU_INCLUDE_BOUNDEDALLOCATOR_H_
18
19#include <limits>
20#include <memory_resource>
21#include <atomic>
22#include <new>
23#include <vector>
24
25#if !defined(__HIPCC__) && !defined(__CUDACC__)
26#include <format>
27#include <string>
28#include "GPUCommonLogger.h"
29#endif
32
33namespace o2::its
34{
35
36// #define BOUNDED_MR_STATS
38{
39 public:
40 class MemoryLimitExceeded final : public std::bad_alloc
41 {
42 public:
43 MemoryLimitExceeded(size_t attempted, size_t used, size_t max)
44 {
45 char buf[256];
46 if (attempted != 0) {
47 (void)snprintf(buf, sizeof(buf), "Reached set memory limit (attempted: %zu, used: %zu, max: %zu)", attempted, used, max);
48 } else {
49 (void)snprintf(buf, sizeof(buf), "New set maximum below current used (newMax: %zu, used: %zu)", max, used);
50 }
51 mMsg = buf;
52 }
53 const char* what() const noexcept final { return mMsg.c_str(); }
54
55 private:
56 std::string mMsg;
57 };
58
59 BoundedMemoryResource(size_t maxBytes = std::numeric_limits<size_t>::max(),
60 std::pmr::memory_resource* upstream = std::pmr::get_default_resource())
61 : mMaxMemory(maxBytes), mUpstream(upstream) {}
62
64 size_t maxBytes = std::numeric_limits<size_t>::max())
65 : mMaxMemory(maxBytes),
66 mAdaptor(std::make_unique<ExternalAllocatorAdaptor>(alloc)),
67 mUpstream(mAdaptor.get()) {}
68
69 void* do_allocate(size_t bytes, size_t alignment) final
70 {
71 size_t new_used{0};
72 size_t current_used{mUsedMemory.load(std::memory_order_relaxed)};
73 do {
74 new_used = current_used + bytes;
75 if (new_used > mMaxMemory.load(std::memory_order_relaxed)) {
76 mCountThrow.fetch_add(1, std::memory_order_relaxed);
77 throw MemoryLimitExceeded(new_used, current_used,
78 mMaxMemory.load(std::memory_order_relaxed));
79 }
80 } while (!mUsedMemory.compare_exchange_weak(current_used, new_used,
81 std::memory_order_acq_rel,
82 std::memory_order_relaxed));
83
84 void* p{nullptr};
85 try {
86 p = mUpstream->allocate(bytes, alignment);
87 } catch (...) {
88 mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed);
89#ifdef BOUNDED_MR_STATS
90 mStats.upstreamFailures.fetch_add(1, std::memory_order_relaxed);
91#endif
92 throw;
93 }
94
95 size_t peak = mPeakUsedMemory.load(std::memory_order_relaxed);
96 while (new_used > peak &&
97 !mPeakUsedMemory.compare_exchange_weak(peak, new_used,
98 std::memory_order_relaxed)) {
99 }
100
101#ifdef BOUNDED_MR_STATS
102 size_t statsPeak = mStats.peak.load(std::memory_order_relaxed);
103 while (new_used > statsPeak &&
104 !mStats.peak.compare_exchange_weak(statsPeak, new_used,
105 std::memory_order_relaxed)) {
106 }
107 mStats.live.fetch_add(1, std::memory_order_relaxed);
108 mStats.nAlloc.fetch_add(1, std::memory_order_relaxed);
109 mStats.totalAlloc.fetch_add(bytes, std::memory_order_relaxed);
110
111 size_t ma = mStats.maxAlign.load(std::memory_order_relaxed);
112 while (alignment > ma && !mStats.maxAlign.compare_exchange_weak(ma, alignment, std::memory_order_relaxed)) {
113 }
114#endif
115 return p;
116 }
117
118 void do_deallocate(void* p, size_t bytes, size_t alignment) final
119 {
120 mUpstream->deallocate(p, bytes, alignment);
121 mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed);
122#ifdef BOUNDED_MR_STATS
123 mStats.live.fetch_sub(1, std::memory_order_relaxed);
124 mStats.nFree.fetch_add(1, std::memory_order_relaxed);
125 mStats.totalFreed.fetch_add(bytes, std::memory_order_relaxed);
126#endif
127 }
128
129 bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final
130 {
131 return this == &other;
132 }
133
134 [[nodiscard]] size_t getUsedMemory() const noexcept
135 {
136 return mUsedMemory.load(std::memory_order_relaxed);
137 }
138 [[nodiscard]] size_t getMaxMemory() const noexcept
139 {
140 return mMaxMemory.load(std::memory_order_relaxed);
141 }
142 [[nodiscard]] size_t getThrowCount() const noexcept
143 {
144 return mCountThrow.load(std::memory_order_relaxed);
145 }
146 [[nodiscard]] size_t getPeakMemory() const noexcept
147 {
148 return mPeakUsedMemory.load(std::memory_order_relaxed);
149 }
150 [[nodiscard]] size_t getPeakMemoryDelta() const noexcept
151 {
152 const size_t peak = mPeakUsedMemory.load(std::memory_order_relaxed);
153 const size_t baseline = mPeakBaselineMemory.load(std::memory_order_relaxed);
154 return peak > baseline ? peak - baseline : 0;
155 }
156
157 void resetPeakMemory() noexcept
158 {
159 const size_t used = mUsedMemory.load(std::memory_order_acquire);
160 mPeakBaselineMemory.store(used, std::memory_order_release);
161 mPeakUsedMemory.store(used, std::memory_order_release);
162 }
163
164 void setMaxMemory(size_t max)
165 {
166 size_t current = mMaxMemory.load(std::memory_order_relaxed);
167 if (max == current) {
168 return;
169 }
170 for (;;) {
171 size_t used = mUsedMemory.load(std::memory_order_acquire);
172 if (used > max) {
173 mCountThrow.fetch_add(1, std::memory_order_relaxed);
174 throw MemoryLimitExceeded(0, used, max);
175 }
176 if (mMaxMemory.compare_exchange_weak(current, max,
177 std::memory_order_release,
178 std::memory_order_relaxed)) {
179 return;
180 }
181 if (current == max) {
182 return;
183 }
184 }
185 }
186
187#if !defined(__HIPCC__) && !defined(__CUDACC__)
188 std::string asString() const
189 {
190 const auto throw_ = mCountThrow.load(std::memory_order_relaxed);
191 const auto used = static_cast<double>(mUsedMemory.load(std::memory_order_relaxed));
192 const auto peak = static_cast<double>(mPeakUsedMemory.load(std::memory_order_relaxed));
193 const auto peakDelta = static_cast<double>(getPeakMemoryDelta());
194 const auto maxm = mMaxMemory.load(std::memory_order_relaxed);
195 std::string ret;
196 if (maxm == std::numeric_limits<size_t>::max()) {
197 ret += std::format("maxthrow={} maxmem=unbounded used={:.2f} GB stagepeak={:.2f} GB stagealloc={:.2f} GB", throw_, used / constants::GB, peak / constants::GB, peakDelta / constants::GB);
198 } else {
199 ret += std::format("maxthrow={} maxmem={:.2f} GB used={:.2f} GB ({:.2f}%) stagepeak={:.2f} GB stagealloc={:.2f} GB", throw_, (double)maxm / constants::GB, used / constants::GB, 100.0 * used / (double)maxm, peak / constants::GB, peakDelta / constants::GB);
200 }
201#ifdef BOUNDED_MR_STATS
202 ret += std::format(" peak={:.2f} GB live={} nAlloc={} nFree={} totalAlloc={:.2f} GB totalFreed={:.2f} GB maxAlign={} upstreamFail={}",
203 (float)mStats.peak.load(std::memory_order_relaxed) / constants::GB,
204 mStats.live.load(std::memory_order_relaxed),
205 mStats.nAlloc.load(std::memory_order_relaxed),
206 mStats.nFree.load(std::memory_order_relaxed),
207 (float)mStats.totalAlloc.load(std::memory_order_relaxed) / constants::GB,
208 (float)mStats.totalFreed.load(std::memory_order_relaxed) / constants::GB,
209 mStats.maxAlign.load(std::memory_order_relaxed),
210 mStats.upstreamFailures.load(std::memory_order_relaxed));
211#endif
212 return ret;
213 }
214
215 void print() const
216 {
217 LOGP(info, "{}", asString());
218 }
219#endif
220
221 private:
222 std::atomic<size_t> mMaxMemory{std::numeric_limits<size_t>::max()};
223 std::atomic<size_t> mCountThrow{0};
224 std::atomic<size_t> mUsedMemory{0};
225 std::atomic<size_t> mPeakUsedMemory{0};
226 std::atomic<size_t> mPeakBaselineMemory{0};
227 std::unique_ptr<ExternalAllocatorAdaptor> mAdaptor{nullptr};
228 std::pmr::memory_resource* mUpstream{nullptr};
229
230#ifdef BOUNDED_MR_STATS
231 struct Stats {
232 std::atomic<size_t> peak{0};
233 std::atomic<size_t> live{0};
234 std::atomic<size_t> nAlloc{0};
235 std::atomic<size_t> nFree{0};
236 std::atomic<size_t> totalAlloc{0};
237 std::atomic<size_t> totalFreed{0};
238 std::atomic<size_t> maxAlign{0};
239 std::atomic<size_t> upstreamFailures{0};
240 };
241 Stats mStats{};
242#endif
243};
244
245template <typename T>
246using bounded_vector = std::pmr::vector<T>;
247
248template <typename T>
249inline void deepVectorClear(std::vector<T>& vec)
250{
251 std::vector<T>().swap(vec);
252}
253
254template <typename T>
256{
257 std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource();
258 vec.~bounded_vector<T>();
259 new (&vec) bounded_vector<T>(std::pmr::polymorphic_allocator<T>{tmr});
260}
261
262template <typename T>
263inline void deepVectorClear(std::vector<bounded_vector<T>>& vec, std::pmr::memory_resource* mr = nullptr)
264{
265 for (auto& v : vec) {
266 deepVectorClear(v, mr);
267 }
268}
269
270template <typename T, size_t S>
271inline void deepVectorClear(std::array<bounded_vector<T>, S>& arr, std::pmr::memory_resource* mr = nullptr)
272{
273 for (size_t i{0}; i < S; ++i) {
274 deepVectorClear(arr[i], mr);
275 }
276}
277
278template <typename T>
279inline void clearResizeBoundedVector(bounded_vector<T>& vec, size_t sz, std::pmr::memory_resource* mr = nullptr, T def = T())
280{
281 std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource();
282 vec.~bounded_vector<T>();
283 new (&vec) bounded_vector<T>(sz, def, std::pmr::polymorphic_allocator<T>{tmr});
284}
285
286template <typename T>
288{
289 vec.clear();
290 vec.reserve(size);
291 for (size_t i = 0; i < size; ++i) {
292 vec.emplace_back(std::pmr::polymorphic_allocator<bounded_vector<T>>{mr});
293 }
294}
295
296template <typename T, size_t S>
297inline void clearResizeBoundedArray(std::array<bounded_vector<T>, S>& arr, size_t size, std::pmr::memory_resource* mr = nullptr, T def = T())
298{
299 for (size_t i{0}; i < S; ++i) {
300 clearResizeBoundedVector(arr[i], size, mr, def);
301 }
302}
303
304template <typename T>
305inline std::vector<T> toSTDVector(const bounded_vector<T>& b)
306{
307 std::vector<T> t(b.size());
308 std::copy(b.cbegin(), b.cend(), t.begin());
309 return t;
310}
311
312} // namespace o2::its
313
314#endif
int32_t i
MemoryLimitExceeded(size_t attempted, size_t used, size_t max)
void * do_allocate(size_t bytes, size_t alignment) final
size_t getPeakMemory() const noexcept
size_t getPeakMemoryDelta() const noexcept
BoundedMemoryResource(ExternalAllocator *alloc, size_t maxBytes=std::numeric_limits< size_t >::max())
void do_deallocate(void *p, size_t bytes, size_t alignment) final
size_t getMaxMemory() const noexcept
bool do_is_equal(const std::pmr::memory_resource &other) const noexcept final
BoundedMemoryResource(size_t maxBytes=std::numeric_limits< size_t >::max(), std::pmr::memory_resource *upstream=std::pmr::get_default_resource())
size_t getUsedMemory() const noexcept
size_t getThrowCount() const noexcept
GLsizeiptr size
Definition glcorearb.h:659
const GLdouble * v
Definition glcorearb.h:832
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
auto get(const std::byte *buffer, size_t=0)
Definition DataHeader.h:454
constexpr float GB
Definition Constants.h:27
void deepVectorClear(std::vector< T > &vec)
std::pmr::vector< T > bounded_vector
std::vector< T > toSTDVector(const bounded_vector< T > &b)
void clearResizeBoundedArray(std::array< bounded_vector< T >, S > &arr, size_t size, std::pmr::memory_resource *mr=nullptr, T def=T())
void clearResizeBoundedVector(bounded_vector< T > &vec, size_t sz, std::pmr::memory_resource *mr=nullptr, T def=T())
constexpr size_t max
VectorOfTObjectPtrs other
std::vector< o2::ctf::BufferType > vec