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