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. Since
16 * both were using the same logic, the functionality has been put in this class.
17 * This class solves RooFit's static destruction order problems by intentionally leaking
18 * arenas of the mempool that still contain live objects at the end of the program.
19 *
20 * When the set types are compared based on a unique ID instead of their pointer,
21 * one can go back to normal memory management, and this class becomes obsolete.
22 */
23
24#ifndef ROOFIT_ROOFITCORE_SRC_MEMPOOLFORROOSETS_H_
25#define ROOFIT_ROOFITCORE_SRC_MEMPOOLFORROOSETS_H_
26
27#include <algorithm>
28#include <array>
29#include <bitset>
30#include <vector>
31
32template <class RooSet_t, std::size_t POOLSIZE>
34
35 struct Arena {
37 : ownedMemory{static_cast<RooSet_t *>(::operator new(2 * POOLSIZE * sizeof(RooSet_t)))},
39 memEnd{memBegin + 2 * POOLSIZE},
40 cycle{{}}
41 {}
42
43 Arena(const Arena &) = delete;
44 Arena(Arena && other)
45 : ownedMemory{other.ownedMemory},
46 memBegin{other.memBegin}, nextItem{other.nextItem}, memEnd{other.memEnd},
47 refCount{other.refCount},
48 totCount{other.totCount},
49 assigned{other.assigned},
50 cycle{{}}
51 {
52 // Needed for unique ownership
53 other.ownedMemory = nullptr;
54 other.refCount = 0;
55 other.totCount = 0;
56 other.assigned = 0;
57 }
58
59 Arena & operator=(const Arena &) = delete;
60 Arena & operator=(Arena && other)
61 {
62 ownedMemory = other.ownedMemory;
63 memBegin = other.memBegin;
64 nextItem = other.nextItem;
65 memEnd = other.memEnd;
66 refCount = other.refCount;
67 totCount = other.totCount;
68 assigned = other.assigned;
69
70 other.ownedMemory = nullptr;
71 other.refCount = 0;
72 other.totCount = 0;
73 other.assigned = 0;
74
75 return *this;
76 }
77
78 // If there is any user left, the arena shouldn't be deleted.
79 // If this happens, nevertheless, one has an order of destruction problem.
81 {
82 if (!ownedMemory) return;
83
84 if (refCount != 0) {
85 std::cerr << __FILE__ << ":" << __LINE__ << "Deleting arena " << ownedMemory << " with use count " << refCount
86 << std::endl;
87 assert(false);
88 }
89
90 ::operator delete(ownedMemory);
91 }
92
93
94 bool inPool(const RooSet_t * const ptr) const {
95 return memBegin <= ptr && ptr < memEnd;
96 }
97
98 bool inPool(const void * const ptr) const
99 {
100 return inPool(static_cast<const RooSet_t * const>(ptr));
101 }
102
103 bool hasSpace() const {
104 return totCount < POOLSIZE * sizeof(RooSet_t) && refCount < POOLSIZE;
105 }
106 bool empty() const { return refCount == 0; }
107
108 void tryFree(bool freeNonFull) {
109 if (ownedMemory && empty() && (!hasSpace() || freeNonFull) ) {
110 ::operator delete(ownedMemory);
111 ownedMemory = nullptr;
112 }
113 }
114
115 void * tryAllocate()
116 {
117 if (!hasSpace()) return nullptr;
118
119 for(std::size_t i = 0; i < POOLSIZE; ++i) {
120 if (nextItem == memEnd) {
121 nextItem = ownedMemory;
122 }
123 std::size_t index = (static_cast<RooSet_t *>(nextItem) - memBegin) / 2;
124 nextItem += 2;
125 if(!assigned[index]) {
126 if (cycle[index] == sizeof(RooSet_t)) {
127 continue;
128 }
129 ++refCount;
130 ++totCount;
131 assigned[index] = true;
132 auto ptr = reinterpret_cast<RooSet_t*>(reinterpret_cast<char*>(ownedMemory + 2 * index) + cycle[index]);
133 cycle[index]++;
134 return ptr;
135 }
136 }
137
138 return nullptr;
139 }
140
141 bool tryDeallocate(void * ptr)
142 {
143 if (inPool(ptr)) {
144 --refCount;
145 tryFree(false);
146 const std::size_t index = ( (reinterpret_cast<const char *>(ptr) - reinterpret_cast<const char *>(memBegin)) / 2) / sizeof(RooSet_t);
147#ifndef NDEBUG
148 if (assigned[index] == false) {
149 std::cerr << "Double delete of " << ptr << " at index " << index << " in Arena with refCount " << refCount
150 << ".\n\tArena: |" << memBegin << "\t" << ptr << "\t" << memEnd << "|" << std::endl;
151 throw;
152 }
153#endif
154 assigned[index] = false;
155 return true;
156 } else
157 return false;
158 }
159
160 bool memoryOverlaps(const Arena& other) const {
161 //Need the reinterpret_cast to correctly check for non-overlap on the last byte of the last element
162 return inPool(other.memBegin) || inPool(reinterpret_cast<const char*>(other.memEnd)-1);
163 }
164
165 RooSet_t * ownedMemory;
166 const RooSet_t * memBegin;
167 RooSet_t * nextItem;
168 const RooSet_t * memEnd;
169 std::size_t refCount = 0;
170 std::size_t totCount = 0;
171
172 std::bitset<POOLSIZE> assigned = {};
173 std::array<int, POOLSIZE> cycle = {{}};
174 };
175
176
177 public:
178 /// Create empty mem pool.
179 MemPoolForRooSets() : fArenas{} {}
180
185
186 /// Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
188 {
189 if (!empty()) {
190#ifndef _MSC_VER
191 std::cerr << __PRETTY_FUNCTION__;
192#endif
193 std::cerr << " The mem pool being deleted is not empty. This will lead to crashes."
194 << std::endl;
195 assert(false);
196 }
197 }
198
199 /// Allocate memory for the templated set type. Fails if bytes != sizeof(RooSet_t).
200 void * allocate(std::size_t bytes)
201 {
202 if (bytes != sizeof(RooSet_t))
203 throw std::bad_alloc();
204
205 if (fArenas.empty()) {
206 newArena();
207 }
208
209 void * ptr = fArenas.back().tryAllocate();
210
211 if (ptr == nullptr) {
212 newArena();
213 prune();
214 ptr = fArenas.back().tryAllocate();
215 }
216
217 assert(ptr != nullptr);
218
219 return ptr;
220 }
221
222
223
224 /// Deallocate memory for the templated set type if in pool.
225 /// \return True if element was in pool.
226 bool deallocate(void * ptr)
227 {
228 bool deallocSuccess = false;
229
230 if (std::any_of(fArenas.begin(), fArenas.end(),
231 [ptr](Arena& arena){return arena.tryDeallocate(ptr);})) {
232 deallocSuccess = true;
233 }
234
235 if (fTeardownMode) {
236 // Try pruning after each dealloc because we are tearing down
237 prune();
238 }
239
240 return deallocSuccess;
241 }
242
243
244
245 ////////////////////////////////////////////////////////////////////////////////
246 /// Free memory in arenas that don't have space and no users.
247 /// In fTeardownMode, it will also delete the arena that still has space.
248 ///
249 void prune()
250 {
251 for (auto & arena : fArenas) {
252 arena.tryFree(fTeardownMode);
253 }
254
255 if (fTeardownMode) {
256 fArenas.erase(
257 std::remove_if(fArenas.begin(), fArenas.end(), [](Arena& ar){return ar.ownedMemory == nullptr;}),
258 fArenas.end());
259 }
260 }
261
262
263
264 /// Test if pool is empty.
265 bool empty() const
266 {
267 return std::all_of(fArenas.begin(), fArenas.end(), [](const Arena & ar) { return ar.empty(); });
268 }
269
270
271
272 /// Set pool to teardown mode (at program end).
273 /// Will prune all empty arenas. Non-empty arenas will survive until all contained elements
274 /// are deleted. They may therefore leak if not all elements are destructed.
275 void teardown()
276 {
277 fTeardownMode = true;
278
279 prune();
280 }
281
282
283 private:
284
285 ////////////////////////////////////////////////////////////////////////////////////
286 /// RooFit relies on unique pointers for RooArgSets. Here, memory
287 /// has to be allocated until a completely new chunk of memory is encountered.
288 /// As soon as RooXXXSets can be identified with a unique ID, this becomes obsolete.
289 void newArena() {
290 std::vector<Arena> failedAllocs;
291 while (true) {
292 Arena ar;
293 if (std::none_of(fArenas.begin(), fArenas.end(),
294 [&ar](Arena& other){return ar.memoryOverlaps(other);})) {
295 fArenas.emplace_back(std::move(ar));
296 break;
297 }
298 else {
299 failedAllocs.push_back(std::move(ar));
300 }
301 }
302 }
303
304
305
306 std::vector<Arena> fArenas;
307 bool fTeardownMode{false};
308};
309
310#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.
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