Logo ROOT   6.16/01
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 <vector>
28#include <algorithm>
29
30template <class RooSet_t, std::size_t POOLSIZE>
32
33 struct Arena {
35 : ownedMemory{static_cast<RooSet_t *>(::operator new(POOLSIZE * sizeof(RooSet_t)))},
37 memEnd{memBegin + POOLSIZE}, refCount{0}
38 {
39 }
40
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#ifndef NDEBUG
49 , deletedElements { std::move(other.deletedElements) }
50#endif
51 {
52 // Needed for unique ownership
53 other.ownedMemory = nullptr;
54 other.refCount = 0;
55 }
56
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#ifndef NDEBUG
67 deletedElements = std::move(other.deletedElements);
68#endif
69 refCount = other.refCount;
70
71 other.ownedMemory = nullptr;
72 other.refCount = 0;
73
74 return *this;
75 }
76
77
78
79 // If there is any user left, the arena shouldn't be deleted.
80 // If this happens, nevertheless, one has an order of destruction problem.
82 {
83 if (!ownedMemory) return;
84
85 if (refCount != 0) {
86 std::cerr << __FILE__ << ":" << __LINE__ << "Deleting arena " << ownedMemory << " with use count " << refCount
87 << std::endl;
88 assert(false);
89 }
90
91 ::operator delete(ownedMemory);
92 }
93
94
95 bool inPool(const RooSet_t * const ptr) const {
96 return memBegin <= ptr && ptr < memEnd;
97 }
98
99 bool inPool(const void * const ptr) const
100 {
101 return inPool(static_cast<const RooSet_t * const>(ptr));
102 }
103
104 bool hasSpace() const { return ownedMemory && nextItem < memEnd; }
105 bool empty() const { return refCount == 0; }
106
107 void tryFree(bool freeNonFull) {
108 if (ownedMemory && empty() && (!hasSpace() || freeNonFull) ) {
109 ::operator delete(ownedMemory);
110 ownedMemory = nullptr;
111 }
112 }
113
114 void * tryAllocate()
115 {
116 if (!hasSpace()) return nullptr;
117
118 ++refCount;
119 return nextItem++;
120 }
121
122 bool tryDeallocate(void * ptr)
123 {
124 if (inPool(ptr)) {
125 --refCount;
126#ifndef NDEBUG
127 const std::size_t index = static_cast<RooSet_t *>(ptr) - memBegin;
128 if (deletedElements.count(index) != 0) {
129 std::cerr << "Double delete of " << ptr << " at index " << index << " in Arena with refCount " << refCount
130 << ".\n\tArena: |" << memBegin << "\t" << ptr << "\t" << memEnd << "|" << std::endl;
131 throw;
132 }
133 deletedElements.insert(index);
134#endif
135 return true;
136 } else
137 return false;
138 }
139
140 bool memoryOverlaps(const Arena& other) const {
141 //Need the reinterpret_cast to correctly check for non-overlap on the last byte of the last element
142 return inPool(other.memBegin) || inPool(reinterpret_cast<const char*>(other.memEnd)-1);
143 }
144
145 RooSet_t * ownedMemory;
146 const RooSet_t * memBegin;
147 RooSet_t * nextItem;
148 const RooSet_t * memEnd;
149 std::size_t refCount;
150#ifndef NDEBUG
151 std::set<std::size_t> deletedElements;
152#endif
153 };
154
155
156 public:
157 /// Create empty mem pool.
159
164
165 /// Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
167 {
168 if (!empty()) {
169#ifndef _MSC_VER
170 std::cerr << __PRETTY_FUNCTION__;
171#endif
172 std::cerr << " The mem pool being deleted is not empty. This will lead to crashes."
173 << std::endl;
174 assert(false);
175 }
176 }
177
178
179
180 /// Allocate memory for the templated set type. Fails if bytes != sizeof(RooSet_t).
181 void * allocate(std::size_t bytes)
182 {
183 if (bytes != sizeof(RooSet_t))
184 throw std::bad_alloc();
185
186 if (fArenas.empty() || !fArenas.back().hasSpace()) {
187 newArena();
188 prune();
189 }
190
191 void * ptr = fArenas.back().tryAllocate();
192 assert(ptr != nullptr);
193
194 return ptr;
195 }
196
197
198
199 /// Deallocate memory for the templated set type if in pool.
200 /// \return True if element was in pool.
201 bool deallocate(void * ptr)
202 {
203 bool deallocSuccess = false;
204
205 if (std::any_of(fArenas.begin(), fArenas.end(),
206 [ptr](Arena& arena){return arena.tryDeallocate(ptr);})) {
207 deallocSuccess = true;
208 }
209
210 if (fTeardownMode) {
211 // Try pruning after each dealloc because we are tearing down
212 prune();
213 }
214
215 return deallocSuccess;
216 }
217
218
219
220 ////////////////////////////////////////////////////////////////////////////////
221 /// Free memory in arenas that don't have space and no users.
222 /// In fTeardownMode, it will also delete the arena that still has space.
223 ///
224 void prune()
225 {
226 for (auto & arena : fArenas) {
227 arena.tryFree(fTeardownMode);
228 }
229
230 if (fTeardownMode) {
231 fArenas.erase(
232 std::remove_if(fArenas.begin(), fArenas.end(), [](Arena& ar){return ar.ownedMemory == nullptr;}),
233 fArenas.end());
234 }
235 }
236
237
238
239 /// Test if pool is empty.
240 bool empty() const
241 {
242 return std::all_of(fArenas.begin(), fArenas.end(), [](const Arena & ar) { return ar.empty(); });
243 }
244
245
246
247 /// Set pool to teardown mode (at program end).
248 /// Will prune all empty arenas. Non-empty arenas will survive until all contained elements
249 /// are deleted. They may therefore leak if not all elements are destructed.
250 void teardown()
251 {
252 fTeardownMode = true;
253
254 prune();
255 }
256
257
258 private:
259
260 ////////////////////////////////////////////////////////////////////////////////////
261 /// RooFit relies on unique pointers for RooArgSets. Here, memory
262 /// has to be allocated until a completely new chunk of memory is encountered.
263 /// As soon as RooXXXSets can be identified with a unique ID, this becomes obsolete.
264 void newArena() {
265 std::vector<Arena> failedAllocs;
266 while (true) {
267 Arena ar;
268 if (std::none_of(fArenas.begin(), fArenas.end(),
269 [&ar](Arena& other){return ar.memoryOverlaps(other);})) {
270 fArenas.push_back(std::move(ar));
271 break;
272 }
273 else {
274 failedAllocs.push_back(std::move(ar));
275 }
276 }
277 }
278
279
280
281 std::vector<Arena> fArenas;
282 bool fTeardownMode{false};
283};
284
285#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.
STL namespace.
void tryFree(bool freeNonFull)
bool inPool(const void *const ptr) const
bool memoryOverlaps(const Arena &other) const
std::set< std::size_t > deletedElements
Arena & operator=(Arena &&other)
bool inPool(const RooSet_t *const ptr) const
Arena(const Arena &)=delete
Arena & operator=(const Arena &)=delete