Logo ROOT  
Reference Guide
MemPoolForRooSets.h
Go to the documentation of this file.
1// @(#)root/roofit:$Id$
2// Author: Stephan Hageboeck, CERN, 10/2018
3/*************************************************************************
4 * Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. *
5 * All rights reserved. *
6 * *
7 * For the licensing terms see $ROOTSYS/LICENSE. *
8 * For the list of contributors see $ROOTSYS/README/CREDITS. *
9 *************************************************************************/
10
11/** Memory pool for RooArgSet and RooDataSet.
12 * \class MemPoolForRooSets
13 * \ingroup roofitcore
14 * RooArgSet and RooDataSet were using a mempool that guarantees that allocating,
15 * de-allocating and re-allocating a set does not yield the same pointer.
16 * RooFit relies on this, unfortunately, because it compares the pointers of RooArgSets
17 * to figure out caching, e.g. of integrals.
18 *
19 * Since both RooArgSet and RooDataSet were using the same logic to manage their memory pools,
20 * the functionality has been put here in a single place.
21 * The introduction of this common mempool also solved RooFit's static destruction order
22 * problems by letting arenas of the mempool leak if RooArgSets are still alive.
23 * This is necessary if the tear down of the mempool happens before all RooArgSets of the entire
24 * process have been deleted. This might e.g. happen in static configs for integrators or when a
25 * plot of a PDF is alive when quitting the interpreter.
26 *
27 * ### If this memory pool seems to leak memory:
28 * - It is likely a leaking RooArgSet / RooDataSet
29 * - Disable the memory pool using the `#define` in RooArgSet.h / RooDataSet.h
30 * - Rerun the leak check to find the leaking RooXSet
31 * - Fix it
32 * - Re-enable the memory pool
33 *
34 * \warning Disabling the memory pools might seem to work at first sight, but can eventually
35 * lead to wrong computations. This would happen if the operating system decides
36 * to assign the same memory address when a RooArgSet is deleted and re-allocated, and both the deleted
37 * as well as the new set happen to be used in the same computation graph. RooFit will
38 * think that the cache doesn't have to be recalculated, and will return an outdated result.
39 * These errors are hard to track down, because they might only happen in a specific toy
40 * MC run on a specific OS / architecture.
41 *
42 * ### How to get rid of the memory pool
43 * If RooArgSet or RooDataSet were compared based on a unique ID instead of their pointer,
44 * this class would become obsolete.
45 * It should be tested, though, if handing memory management over to the OS has an impact on speed.
46 * This is less of a worry, though, because OSs got smarter over RooFit's life time.
47 */
48
49#ifndef ROOFIT_ROOFITCORE_SRC_MEMPOOLFORROOSETS_H_
50#define ROOFIT_ROOFITCORE_SRC_MEMPOOLFORROOSETS_H_
51
52#include "TStorage.h"
53
54#include <algorithm>
55#include <array>
56#include <bitset>
57#include <vector>
58
59template <class RooSet_t, std::size_t POOLSIZE>
61
62 struct Arena {
64 : ownedMemory{static_cast<RooSet_t *>(TStorage::ObjectAlloc(2 * POOLSIZE * sizeof(RooSet_t)))},
66 memEnd{memBegin + 2 * POOLSIZE},
67 cycle{{}}
68 {}
69
70 Arena(const Arena &) = delete;
71 Arena(Arena && other)
72 : ownedMemory{other.ownedMemory},
73 memBegin{other.memBegin}, nextItem{other.nextItem}, memEnd{other.memEnd},
74 refCount{other.refCount},
75 totCount{other.totCount},
76 assigned{other.assigned},
77 cycle{{}}
78 {
79 // Needed for unique ownership
80 other.ownedMemory = nullptr;
81 other.refCount = 0;
82 other.totCount = 0;
83 other.assigned = 0;
84 }
85
86 Arena & operator=(const Arena &) = delete;
87 Arena & operator=(Arena && other)
88 {
89 ownedMemory = other.ownedMemory;
90 memBegin = other.memBegin;
91 nextItem = other.nextItem;
92 memEnd = other.memEnd;
93 refCount = other.refCount;
94 totCount = other.totCount;
95 assigned = other.assigned;
96
97 other.ownedMemory = nullptr;
98 other.refCount = 0;
99 other.totCount = 0;
100 other.assigned = 0;
101
102 return *this;
103 }
104
105 // If there is any user left, the arena shouldn't be deleted.
106 // If this happens, nevertheless, one has an order of destruction problem.
108 {
109 if (!ownedMemory) return;
110
111 if (refCount != 0) {
112 std::cerr << __FILE__ << ":" << __LINE__ << "Deleting arena " << ownedMemory << " with use count " << refCount
113 << std::endl;
114 assert(false);
115 }
116
117 ::operator delete(ownedMemory);
118 }
119
120
121 bool inPool(const RooSet_t * const ptr) const {
122 return memBegin <= ptr && ptr < memEnd;
123 }
124
125 bool inPool(const void * const ptr) const
126 {
127 return inPool(static_cast<const RooSet_t * const>(ptr));
128 }
129
130 bool hasSpace() const {
131 return totCount < POOLSIZE * sizeof(RooSet_t) && refCount < POOLSIZE;
132 }
133 bool empty() const { return refCount == 0; }
134
135 void tryFree(bool freeNonFull) {
136 if (ownedMemory && empty() && (!hasSpace() || freeNonFull) ) {
137 ::operator delete(ownedMemory);
138 ownedMemory = nullptr;
139 }
140 }
141
142 void * tryAllocate()
143 {
144 if (!hasSpace()) return nullptr;
145
146 for(std::size_t i = 0; i < POOLSIZE; ++i) {
147 if (nextItem == memEnd) {
148 nextItem = ownedMemory;
149 }
150 std::size_t index = (static_cast<RooSet_t *>(nextItem) - memBegin) / 2;
151 nextItem += 2;
152 if(!assigned[index]) {
153 if (cycle[index] == sizeof(RooSet_t)) {
154 continue;
155 }
156 ++refCount;
157 ++totCount;
158 assigned[index] = true;
159 auto ptr = reinterpret_cast<RooSet_t*>(reinterpret_cast<char*>(ownedMemory + 2 * index) + cycle[index]);
160 cycle[index]++;
161 return ptr;
162 }
163 }
164
165 return nullptr;
166 }
167
168 bool tryDeallocate(void * ptr)
169 {
170 if (inPool(ptr)) {
171 --refCount;
172 tryFree(false);
173 const std::size_t index = ( (reinterpret_cast<const char *>(ptr) - reinterpret_cast<const char *>(memBegin)) / 2) / sizeof(RooSet_t);
174#ifndef NDEBUG
175 if (assigned[index] == false) {
176 std::cerr << "Double delete of " << ptr << " at index " << index << " in Arena with refCount " << refCount
177 << ".\n\tArena: |" << memBegin << "\t" << ptr << "\t" << memEnd << "|" << std::endl;
178 throw;
179 }
180#endif
181 assigned[index] = false;
182 return true;
183 } else
184 return false;
185 }
186
187 bool memoryOverlaps(const Arena& other) const {
188 //Need the reinterpret_cast to correctly check for non-overlap on the last byte of the last element
189 return inPool(other.memBegin) || inPool(reinterpret_cast<const char*>(other.memEnd)-1);
190 }
191
192 RooSet_t * ownedMemory;
193 const RooSet_t * memBegin;
194 RooSet_t * nextItem;
195 const RooSet_t * memEnd;
196 std::size_t refCount = 0;
197 std::size_t totCount = 0;
198
199 std::bitset<POOLSIZE> assigned = {};
200 std::array<int, POOLSIZE> cycle = {{}};
201 };
202
203
204 public:
205 /// Create empty mem pool.
206 MemPoolForRooSets() : fArenas{} {}
207
212
213 /// Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
215 {
216 if (!empty()) {
217#ifndef _MSC_VER
218 std::cerr << __PRETTY_FUNCTION__;
219#endif
220 std::cerr << " The mem pool being deleted is not empty. This will lead to crashes."
221 << std::endl;
222 assert(false);
223 }
224 }
225
226 /// Allocate memory for the templated set type. Fails if bytes != sizeof(RooSet_t).
227 void * allocate(std::size_t bytes)
228 {
229 if (bytes != sizeof(RooSet_t))
230 throw std::bad_alloc();
231
232 if (fArenas.empty()) {
233 newArena();
234 }
235
236 void * ptr = fArenas.back().tryAllocate();
237
238 if (ptr == nullptr) {
239 newArena();
240 prune();
241 ptr = fArenas.back().tryAllocate();
242 }
243
244 assert(ptr != nullptr);
245
246 return ptr;
247 }
248
249
250
251 /// Deallocate memory for the templated set type if in pool.
252 /// \return True if element was in pool.
253 bool deallocate(void * ptr)
254 {
255 bool deallocSuccess = false;
256
257 if (std::any_of(fArenas.begin(), fArenas.end(),
258 [ptr](Arena& arena){return arena.tryDeallocate(ptr);})) {
259 deallocSuccess = true;
260 }
261
262 if (fTeardownMode) {
263 // Try pruning after each dealloc because we are tearing down
264 prune();
265 }
266
267 return deallocSuccess;
268 }
269
270
271
272 ////////////////////////////////////////////////////////////////////////////////
273 /// Free memory in arenas that don't have space and no users.
274 /// In fTeardownMode, it will also delete the arena that still has space.
275 /// Arenas are never deleted, because the pointers of RooArgSets/RooDataSets need
276 /// to be unique for RooFit's caching to work. The arenas only give back the memory to
277 /// the OS.
278 void prune()
279 {
280 for (auto & arena : fArenas) {
281 arena.tryFree(fTeardownMode);
282 }
283
284 if (fTeardownMode) {
285 fArenas.erase(
286 std::remove_if(fArenas.begin(), fArenas.end(), [](Arena& ar){return ar.ownedMemory == nullptr;}),
287 fArenas.end());
288 }
289 }
290
291
292
293 /// Test if pool is empty.
294 bool empty() const
295 {
296 return std::all_of(fArenas.begin(), fArenas.end(), [](const Arena & ar) { return ar.empty(); });
297 }
298
299
300
301 /// Set pool to teardown mode (at program end).
302 /// Will prune all empty arenas. Non-empty arenas will survive until all contained elements
303 /// are deleted. They may therefore leak if not all elements are destructed.
304 void teardown()
305 {
306 fTeardownMode = true;
307
308 prune();
309 }
310
311
312 private:
313
314 ////////////////////////////////////////////////////////////////////////////////////
315 /// RooFit relies on unique pointers for RooArgSets. Here, memory
316 /// has to be allocated until a completely new chunk of memory is encountered.
317 /// As soon as RooXXXSets can be identified with a unique ID, this becomes obsolete.
318 void newArena() {
319 std::vector<Arena> failedAllocs;
320 while (true) {
321 Arena ar;
322 if (std::none_of(fArenas.begin(), fArenas.end(),
323 [&ar](Arena& other){return ar.memoryOverlaps(other);})) {
324 fArenas.emplace_back(std::move(ar));
325 break;
326 }
327 else {
328 failedAllocs.push_back(std::move(ar));
329 }
330 }
331 }
332
333
334
335 std::vector<Arena> fArenas;
336 bool fTeardownMode{false};
337};
338
339#endif /* ROOFIT_ROOFITCORE_SRC_MEMPOOLFORROOSETS_H_ */
Memory pool for RooArgSet and RooDataSet.
~MemPoolForRooSets()
Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
void prune()
Free memory in arenas that don't have space and no users.
MemPoolForRooSets & operator=(const MemPoolForRooSets &)=delete
MemPoolForRooSets(const MemPoolForRooSets &)=delete
void newArena()
RooFit relies on unique pointers for RooArgSets.
MemPoolForRooSets & operator=(MemPoolForRooSets &&)=delete
MemPoolForRooSets()
Create empty mem pool.
void * allocate(std::size_t bytes)
Allocate memory for the templated set type. Fails if bytes != sizeof(RooSet_t).
bool deallocate(void *ptr)
Deallocate memory for the templated set type if in pool.
MemPoolForRooSets(MemPoolForRooSets &&)=delete
void teardown()
Set pool to teardown mode (at program end).
std::vector< Arena > fArenas
bool empty() const
Test if pool is empty.
Storage manager.
Definition: TStorage.h:33
void tryFree(bool freeNonFull)
bool inPool(const void *const ptr) const
bool memoryOverlaps(const Arena &other) const
std::array< int, POOLSIZE > cycle
Arena & operator=(Arena &&other)
bool inPool(const RooSet_t *const ptr) const
Arena(const Arena &)=delete
Arena & operator=(const Arena &)=delete