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
60 {
61 static std::pmr::synchronized_pool_resource pool{std::pmr::get_default_resource()};
62 return &pool;
63 }
64
65 BoundedMemoryResource(size_t maxBytes = std::numeric_limits<size_t>::max(),
66 std::pmr::memory_resource* upstream = nullptr)
67 : mMaxMemory(maxBytes), mUpstream(upstream != nullptr ? upstream : cachingUpstream()) {}
68
70 size_t maxBytes = std::numeric_limits<size_t>::max())
71 : mMaxMemory(maxBytes),
72 mAdaptor(std::make_unique<ExternalAllocatorAdaptor>(alloc)),
73 mUpstream(mAdaptor.get()) {}
74
75 void* do_allocate(size_t bytes, size_t alignment) final
76 {
77 size_t new_used{0};
78 size_t current_used{mUsedMemory.load(std::memory_order_relaxed)};
79 do {
80 new_used = current_used + bytes;
81 if (new_used > mMaxMemory.load(std::memory_order_relaxed)) {
82 mCountThrow.fetch_add(1, std::memory_order_relaxed);
83 throw MemoryLimitExceeded(new_used, current_used,
84 mMaxMemory.load(std::memory_order_relaxed));
85 }
86 } while (!mUsedMemory.compare_exchange_weak(current_used, new_used,
87 std::memory_order_acq_rel,
88 std::memory_order_relaxed));
89
90 void* p{nullptr};
91 try {
92 p = mUpstream->allocate(bytes, alignment);
93 } catch (...) {
94 mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed);
95#ifdef BOUNDED_MR_STATS
96 mStats.upstreamFailures.fetch_add(1, std::memory_order_relaxed);
97#endif
98 throw;
99 }
100
101 size_t peak = mPeakUsedMemory.load(std::memory_order_relaxed);
102 while (new_used > peak &&
103 !mPeakUsedMemory.compare_exchange_weak(peak, new_used,
104 std::memory_order_relaxed)) {
105 }
106
107#ifdef BOUNDED_MR_STATS
108 size_t statsPeak = mStats.peak.load(std::memory_order_relaxed);
109 while (new_used > statsPeak &&
110 !mStats.peak.compare_exchange_weak(statsPeak, new_used,
111 std::memory_order_relaxed)) {
112 }
113 mStats.live.fetch_add(1, std::memory_order_relaxed);
114 mStats.nAlloc.fetch_add(1, std::memory_order_relaxed);
115 mStats.totalAlloc.fetch_add(bytes, std::memory_order_relaxed);
116
117 size_t ma = mStats.maxAlign.load(std::memory_order_relaxed);
118 while (alignment > ma && !mStats.maxAlign.compare_exchange_weak(ma, alignment, std::memory_order_relaxed)) {
119 }
120#endif
121 return p;
122 }
123
124 void do_deallocate(void* p, size_t bytes, size_t alignment) final
125 {
126 mUpstream->deallocate(p, bytes, alignment);
127 mUsedMemory.fetch_sub(bytes, std::memory_order_relaxed);
128#ifdef BOUNDED_MR_STATS
129 mStats.live.fetch_sub(1, std::memory_order_relaxed);
130 mStats.nFree.fetch_add(1, std::memory_order_relaxed);
131 mStats.totalFreed.fetch_add(bytes, std::memory_order_relaxed);
132#endif
133 }
134
135 bool do_is_equal(const std::pmr::memory_resource& other) const noexcept final
136 {
137 return this == &other;
138 }
139
140 [[nodiscard]] size_t getUsedMemory() const noexcept
141 {
142 return mUsedMemory.load(std::memory_order_relaxed);
143 }
144 [[nodiscard]] size_t getMaxMemory() const noexcept
145 {
146 return mMaxMemory.load(std::memory_order_relaxed);
147 }
148 [[nodiscard]] size_t getThrowCount() const noexcept
149 {
150 return mCountThrow.load(std::memory_order_relaxed);
151 }
152 [[nodiscard]] size_t getPeakMemory() const noexcept
153 {
154 return mPeakUsedMemory.load(std::memory_order_relaxed);
155 }
156 [[nodiscard]] size_t getPeakMemoryDelta() const noexcept
157 {
158 const size_t peak = mPeakUsedMemory.load(std::memory_order_relaxed);
159 const size_t baseline = mPeakBaselineMemory.load(std::memory_order_relaxed);
160 return peak > baseline ? peak - baseline : 0;
161 }
162
163 void resetPeakMemory() noexcept
164 {
165 const size_t used = mUsedMemory.load(std::memory_order_acquire);
166 mPeakBaselineMemory.store(used, std::memory_order_release);
167 mPeakUsedMemory.store(used, std::memory_order_release);
168 }
169
170 void setMaxMemory(size_t max)
171 {
172 size_t current = mMaxMemory.load(std::memory_order_relaxed);
173 if (max == current) {
174 return;
175 }
176 for (;;) {
177 size_t used = mUsedMemory.load(std::memory_order_acquire);
178 if (used > max) {
179 mCountThrow.fetch_add(1, std::memory_order_relaxed);
180 throw MemoryLimitExceeded(0, used, max);
181 }
182 if (mMaxMemory.compare_exchange_weak(current, max,
183 std::memory_order_release,
184 std::memory_order_relaxed)) {
185 return;
186 }
187 if (current == max) {
188 return;
189 }
190 }
191 }
192
193#if !defined(__HIPCC__) && !defined(__CUDACC__)
194 std::string asString() const
195 {
196 const auto throw_ = mCountThrow.load(std::memory_order_relaxed);
197 const auto used = static_cast<double>(mUsedMemory.load(std::memory_order_relaxed));
198 const auto peak = static_cast<double>(mPeakUsedMemory.load(std::memory_order_relaxed));
199 const auto peakDelta = static_cast<double>(getPeakMemoryDelta());
200 const auto maxm = mMaxMemory.load(std::memory_order_relaxed);
201 std::string ret;
202 if (maxm == std::numeric_limits<size_t>::max()) {
203 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);
204 } else {
205 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);
206 }
207#ifdef BOUNDED_MR_STATS
208 ret += std::format(" peak={:.2f} GB live={} nAlloc={} nFree={} totalAlloc={:.2f} GB totalFreed={:.2f} GB maxAlign={} upstreamFail={}",
209 (float)mStats.peak.load(std::memory_order_relaxed) / constants::GB,
210 mStats.live.load(std::memory_order_relaxed),
211 mStats.nAlloc.load(std::memory_order_relaxed),
212 mStats.nFree.load(std::memory_order_relaxed),
213 (float)mStats.totalAlloc.load(std::memory_order_relaxed) / constants::GB,
214 (float)mStats.totalFreed.load(std::memory_order_relaxed) / constants::GB,
215 mStats.maxAlign.load(std::memory_order_relaxed),
216 mStats.upstreamFailures.load(std::memory_order_relaxed));
217#endif
218 return ret;
219 }
220
221 void print() const
222 {
223 LOGP(info, "{}", asString());
224 }
225#endif
226
227 private:
228 std::atomic<size_t> mMaxMemory{std::numeric_limits<size_t>::max()};
229 std::atomic<size_t> mCountThrow{0};
230 std::atomic<size_t> mUsedMemory{0};
231 std::atomic<size_t> mPeakUsedMemory{0};
232 std::atomic<size_t> mPeakBaselineMemory{0};
233 std::unique_ptr<ExternalAllocatorAdaptor> mAdaptor{nullptr};
234 std::pmr::memory_resource* mUpstream{nullptr};
235
236#ifdef BOUNDED_MR_STATS
237 struct Stats {
238 std::atomic<size_t> peak{0};
239 std::atomic<size_t> live{0};
240 std::atomic<size_t> nAlloc{0};
241 std::atomic<size_t> nFree{0};
242 std::atomic<size_t> totalAlloc{0};
243 std::atomic<size_t> totalFreed{0};
244 std::atomic<size_t> maxAlign{0};
245 std::atomic<size_t> upstreamFailures{0};
246 };
247 Stats mStats{};
248#endif
249};
250
251template <typename T>
252using bounded_vector = std::pmr::vector<T>;
253
254template <typename T>
255inline void deepVectorClear(std::vector<T>& vec)
256{
257 std::vector<T>().swap(vec);
258}
259
260template <typename T>
262{
263 std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource();
264 vec.~bounded_vector<T>();
265 new (&vec) bounded_vector<T>(std::pmr::polymorphic_allocator<T>{tmr});
266}
267
268template <typename T>
269inline void deepVectorClear(std::vector<bounded_vector<T>>& vec, std::pmr::memory_resource* mr = nullptr)
270{
271 for (auto& v : vec) {
272 deepVectorClear(v, mr);
273 }
274}
275
276template <typename T, size_t S>
277inline void deepVectorClear(std::array<bounded_vector<T>, S>& arr, std::pmr::memory_resource* mr = nullptr)
278{
279 for (size_t i{0}; i < S; ++i) {
280 deepVectorClear(arr[i], mr);
281 }
282}
283
284template <typename T>
285inline void clearResizeBoundedVector(bounded_vector<T>& vec, size_t sz, std::pmr::memory_resource* mr = nullptr, T def = T())
286{
287 std::pmr::memory_resource* tmr = (mr != nullptr) ? mr : vec.get_allocator().resource();
288 vec.~bounded_vector<T>();
289 new (&vec) bounded_vector<T>(sz, def, std::pmr::polymorphic_allocator<T>{tmr});
290}
291
292template <typename T>
294{
295 vec.clear();
296 vec.reserve(size);
297 for (size_t i = 0; i < size; ++i) {
298 vec.emplace_back(std::pmr::polymorphic_allocator<bounded_vector<T>>{mr});
299 }
300}
301
302template <typename T, size_t S>
303inline void clearResizeBoundedArray(std::array<bounded_vector<T>, S>& arr, size_t size, std::pmr::memory_resource* mr = nullptr, T def = T())
304{
305 for (size_t i{0}; i < S; ++i) {
306 clearResizeBoundedVector(arr[i], size, mr, def);
307 }
308}
309
310template <typename T>
311inline std::vector<T> toSTDVector(const bounded_vector<T>& b)
312{
313 std::vector<T> t(b.size());
314 std::copy(b.cbegin(), b.cend(), t.begin());
315 return t;
316}
317
318} // namespace o2::its
319
320#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=nullptr)
size_t getUsedMemory() const noexcept
size_t getThrowCount() const noexcept
static std::pmr::memory_resource * cachingUpstream()
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