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 
59 template <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) {
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.
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_ */
MemPoolForRooSets::teardown
void teardown()
Set pool to teardown mode (at program end).
Definition: MemPoolForRooSets.h:304
MemPoolForRooSets::Arena::memoryOverlaps
bool memoryOverlaps(const Arena &other) const
Definition: MemPoolForRooSets.h:187
MemPoolForRooSets::Arena::~Arena
~Arena()
Definition: MemPoolForRooSets.h:107
MemPoolForRooSets::Arena::inPool
bool inPool(const void *const ptr) const
Definition: MemPoolForRooSets.h:125
MemPoolForRooSets::Arena::tryDeallocate
bool tryDeallocate(void *ptr)
Definition: MemPoolForRooSets.h:168
MemPoolForRooSets::Arena::memBegin
const RooSet_t * memBegin
Definition: MemPoolForRooSets.h:193
MemPoolForRooSets::Arena::inPool
bool inPool(const RooSet_t *const ptr) const
Definition: MemPoolForRooSets.h:121
MemPoolForRooSets::Arena::cycle
std::array< int, POOLSIZE > cycle
Definition: MemPoolForRooSets.h:200
MemPoolForRooSets::Arena::nextItem
RooSet_t * nextItem
Definition: MemPoolForRooSets.h:194
MemPoolForRooSets::Arena::ownedMemory
RooSet_t * ownedMemory
Definition: MemPoolForRooSets.h:192
MemPoolForRooSets::Arena::tryAllocate
void * tryAllocate()
Definition: MemPoolForRooSets.h:142
MemPoolForRooSets::Arena::Arena
Arena()
Definition: MemPoolForRooSets.h:63
MemPoolForRooSets::fTeardownMode
bool fTeardownMode
Definition: MemPoolForRooSets.h:336
MemPoolForRooSets::MemPoolForRooSets
MemPoolForRooSets(MemPoolForRooSets &&)=delete
MemPoolForRooSets::Arena::Arena
Arena(const Arena &)=delete
MemPoolForRooSets::MemPoolForRooSets
MemPoolForRooSets(const MemPoolForRooSets &)=delete
TStorage::ObjectAlloc
static void * ObjectAlloc(size_t size)
Used to allocate a TObject on the heap (via TObject::operator new()).
Definition: TStorage.cxx:328
MemPoolForRooSets::operator=
MemPoolForRooSets & operator=(MemPoolForRooSets &&)=delete
MemPoolForRooSets::Arena::Arena
Arena(Arena &&other)
Definition: MemPoolForRooSets.h:71
MemPoolForRooSets::Arena
Definition: MemPoolForRooSets.h:62
TStorage.h
MemPoolForRooSets::fArenas
std::vector< Arena > fArenas
Definition: MemPoolForRooSets.h:335
MemPoolForRooSets::allocate
void * allocate(std::size_t bytes)
Allocate memory for the templated set type. Fails if bytes != sizeof(RooSet_t).
Definition: MemPoolForRooSets.h:227
MemPoolForRooSets::deallocate
bool deallocate(void *ptr)
Deallocate memory for the templated set type if in pool.
Definition: MemPoolForRooSets.h:253
MemPoolForRooSets::Arena::empty
bool empty() const
Definition: MemPoolForRooSets.h:133
MemPoolForRooSets::~MemPoolForRooSets
~MemPoolForRooSets()
Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
Definition: MemPoolForRooSets.h:214
MemPoolForRooSets::Arena::hasSpace
bool hasSpace() const
Definition: MemPoolForRooSets.h:130
MemPoolForRooSets
Memory pool for RooArgSet and RooDataSet.
Definition: MemPoolForRooSets.h:60
MemPoolForRooSets::Arena::refCount
std::size_t refCount
Definition: MemPoolForRooSets.h:196
MemPoolForRooSets::MemPoolForRooSets
MemPoolForRooSets()
Create empty mem pool.
Definition: MemPoolForRooSets.h:206
MemPoolForRooSets::Arena::memEnd
const RooSet_t * memEnd
Definition: MemPoolForRooSets.h:195
MemPoolForRooSets::Arena::operator=
Arena & operator=(Arena &&other)
Definition: MemPoolForRooSets.h:87
MemPoolForRooSets::newArena
void newArena()
RooFit relies on unique pointers for RooArgSets.
Definition: MemPoolForRooSets.h:318
MemPoolForRooSets::Arena::totCount
std::size_t totCount
Definition: MemPoolForRooSets.h:197
MemPoolForRooSets::prune
void prune()
Free memory in arenas that don't have space and no users.
Definition: MemPoolForRooSets.h:278
MemPoolForRooSets::Arena::assigned
std::bitset< POOLSIZE > assigned
Definition: MemPoolForRooSets.h:199
MemPoolForRooSets::Arena::tryFree
void tryFree(bool freeNonFull)
Definition: MemPoolForRooSets.h:135
MemPoolForRooSets::empty
bool empty() const
Test if pool is empty.
Definition: MemPoolForRooSets.h:294
MemPoolForRooSets::operator=
MemPoolForRooSets & operator=(const MemPoolForRooSets &)=delete
MemPoolForRooSets::Arena::operator=
Arena & operator=(const Arena &)=delete