Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
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 
30 template <class RooSet_t, std::size_t POOLSIZE>
31 class MemPoolForRooSets {
32 
33  struct Arena {
34  Arena()
35  : ownedMemory{static_cast<RooSet_t *>(::operator new(POOLSIZE * sizeof(RooSet_t)))},
36  memBegin{ownedMemory}, nextItem{ownedMemory},
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.
81  ~Arena()
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.
158  MemPoolForRooSets() : fArenas{} {}
159 
160  MemPoolForRooSets(const MemPoolForRooSets &) = delete;
161  MemPoolForRooSets(MemPoolForRooSets &&) = delete;
162  MemPoolForRooSets & operator=(const MemPoolForRooSets &) = delete;
163  MemPoolForRooSets & operator=(MemPoolForRooSets &&) = delete;
164 
165  /// Destructor. Should not be called when RooArgSets or RooDataSets are still alive.
166  ~MemPoolForRooSets()
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_ */