Hall-D Software  alpha
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
DResourcePool.h
Go to the documentation of this file.
1 #ifndef DResourcePool_h
2 #define DResourcePool_h
3 
4 #include <atomic>
5 #include <typeinfo>
6 #include <vector>
7 #include <mutex>
8 #include <iostream>
9 #include <type_traits>
10 #include <memory>
11 
12 #include "DResettable.h"
13 
14 using namespace std;
15 
16 /****************************************************** OVERVIEW ******************************************************
17  *
18  * This class can be used to pool resources between pools for a given object type, even on different threads.
19  * The way this works is that, in addition to a "local" resource pool for each instantiated DResourcePool object,
20  * there is a private static resource pool, which is thus shared amongst all pools of that type.
21  *
22  * So, if an object is requested and the local pool is empty, it will then try to retrieve one from the shared pool.
23  * If none exist, it makes a new one.
24  * Also, if you a recycle an object and the local pool is "full," it will save it to the shared pool instead.
25  * The reason there is both a "local" pool and a shared pool is to minimize the locking needed.
26  * Control variables can be set to define exactly how this behaves.
27  *
28  * A pool counter keeps track of how many DResourcePool's there are for a given type.
29  * Once it drops to zero, all of the remaining objects are deleted.
30  *
31  ***************************************** DEFINITION AND USE: NON-SHARED_PTR'S ***************************************
32  *
33  * If you do not intend to retrieve the resources from this pool as shared_ptr's, then just define a pool (in e.g. a factory) as:
34  * DResourcePool<MyClass> dMyClassPool;
35  *
36  * You can then retrieve and recycle resources via the Get_Resource() and Recycle() functions.
37  * Be sure to recycle the memory once you are done with it (e.g. beginning of factory evnt() call) or else it will leak.
38  *
39  ******************************************* DEFINITION AND USE: SHARED_PTR'S *****************************************
40  *
41  * You can retrieve shared_ptr's of the objects by calling the Get_SharedResource() method.
42  * The advantage of using shared_ptr's is that they automatically keep track of when they are out of scope.
43  * These shared_ptr's have been created with a DSharedPtrRecycler functor:
44  * Once the shared_ptr goes out of scope, the contained resource is automatically recycled back to the pool.
45  *
46  * Note that there is a tricky situation that arises when a shared_ptr outlives the life of the DResourcePool.
47  * This can happen if (e.g.) shared_ptr's are stored as members of a factory alongside a DResourcePool, and the pool is destroyed first.
48  * Or if they are created by a thread_local pool, and the objects outlive the thread.
49  *
50  * To combat this, we have the DSharedPtrRecycler hold a weak_ptr to the DResourcePool (where the object gets recycled to).
51  * That way, if the pool has been deleted, the weak_ptr will have expired and we can manually delete the object instead of trying to recycle it.
52  * This only works if the pool itself has been created within a shared_ptr in the first place, so it is recommended that these pools be defined as:
53  *
54  * YOU MUST DO THIS!!
55  * auto dMyClassPool = std::make_shared<DResourcePool<MyClass>>();
56  *
57  *************************************************** FACTORY OBJECTS **************************************************
58  *
59  * If you want the _data objects for the factory to be managed by a resource pool instead of JANA, in the factory init() call:
60  * SetFactoryFlag(NOT_OBJECT_OWNER);
61  * With this flag set, JANA will not delete the objects in _data, but it will clear them (_data.clear()) prior to the evnt() method.
62  * So that means you must also put them in a separate factory vector so that you have a handle on them to recycle them at the beginning of the factory evnt().
63  *
64  *************************************************** CLASS COMPONENTS **************************************************
65  *
66  * Note that the components of the particle classes (DKinematicData, DChargedTrackHypothesis, DNeutralParticleHypothesis) contain shared_ptr's.
67  * That's because the components (kinematics, timing info, etc.) are often identical between objects, and instead of duplicating the memory, it's cheaper to just shared
68  * For example, the kinematics for the pre-kinfit DChargedTrackHypothesis are identical to those from the DTrackTimeBased.
69  * Also, the tracking information for the post-kinfit DChargedTrackHypothesis is identical to the pre-kinfit information.
70  *
71  * The resource pools for these shared_ptr's are defined to be private thread_local within the classes themselves.
72  * That way each thread has an instance of the pool, while still sharing a common pool underneath.
73  *
74  *********************************************** BEWARE: CLASS COMPONENTS **********************************************
75  *
76  * Problem: Creating a resource on one thread, and recycling it on another thread: race condition in the resource pool LOCAL pool.
77  *
78  * How does this happen? You create a charged-hypo on one thread, which creates a DKinematicInfo object on that thread.
79  * You then recycle the hypo, and another thread picks it up. The other thread calls Hypo::Reset(), which clears the DKinematicInfo.
80  * Since it's stored in a shared_ptr, it goes back to the thread it was created.
81  *
82  * Solution 1: Disable intra-thread sharing of objects that CONTAIN shared_ptrs: Everything that is or inherits from DKinematicData
83  * Do this by setting the max size of the shared pool to zero for these types.
84  *
85  * Solution 2: Have those objects inherit from DResettable (below), and define the member functions.
86  *
87  **********************************************************************************************************************/
88 
89 // Apple compiler does not currently support alignas. Make this an empty definition if it is not already defined.
90 #ifndef alignas
91 #define alignas(A)
92 #endif
93 
94 template <typename DType> class DResourcePool : public std::enable_shared_from_this<DResourcePool<DType>>
95 {
96  //TYPE TRAIT REQUIREMENTS
97  //If these statements are false, this won't compile
98  static_assert(!std::is_pointer<DType>::value, "The template type for DResourcePool must not be a pointer (the stored type IS a pointer though).");
99  static_assert(!std::is_const<DType>::value, "The template type for DResourcePool must not be const.");
100  static_assert(!std::is_volatile<DType>::value, "The template type for DResourcePool must not be volatile.");
101 
102  public:
103  DResourcePool(void);
104  DResourcePool(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize);
105  DResourcePool(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize, size_t locMaxSharedPoolSize, size_t locDebugLevel);
106  ~DResourcePool(void);
107 
108  void Set_ControlParams(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize);
109  void Set_ControlParams(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize, size_t locMaxSharedPoolSize, size_t locDebugLevel);
110  DType* Get_Resource(void);
111  shared_ptr<DType> Get_SharedResource(void);
112 
113  //RECYCLE CONST OBJECTS //these methods just const_cast and call the non-const versions
114  void Recycle(const DType* locResource){Recycle(const_cast<DType*>(locResource));}
115  void Recycle(vector<const DType*>& locResources); //move-clears the input vector
116 
117  //RECYCLE NON-CONST OBJECTS
118  void Recycle(DType* locResource);
119  void Recycle(vector<DType*>& locResources); //move-clears the input vector
120 
121  size_t Get_SharedPoolSize(void) const;
122  size_t Get_PoolSize(void) const{return dResourcePool_Local.size();}
123  size_t Get_NumObjectsAllThreads(void) const{return dObjectCounter;}
124 
125  static constexpr unsigned int Get_CacheLineSize(void)
126  {
127  /// Returns the cache line size for the processor of the target platform.
128  /*! The cache line size is useful for creating a buffer to make sure that a variable accessed by multiple threads does not share the cache line it is on.
129  This is useful for variables that may be written-to by one of the threads, because the thread will acquire locked access to the entire cache line.
130  This blocks other threads from operating on the other data stored on the cache line. Note that it is also important to align the shared data as well.
131  See http://www.drdobbs.com/parallel/eliminate-false-sharing/217500206?pgno=4 for more details. */
132 
133  //cache line size is 64 for ifarm1402, gcc won't allow larger than 128
134  //the cache line size is in /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
135  return 64; //units are in bytes
136  }
137 
138  private:
139 
140  //Enable this version if type inherits from DResettable //void: is return type
141  template <typename RType> typename std::enable_if<std::is_base_of<DResettable, RType>::value, void>::type Release_Resources(DResettable* locResource){locResource->Release();}
142  template <typename RType> typename std::enable_if<std::is_base_of<DResettable, RType>::value, void>::type Reset(DResettable* locResource){locResource->Reset();}
143 
144  //Enable this version if type does NOT inherit from DResettable //void: is return type
145  template <typename RType> typename std::enable_if<!std::is_base_of<DResettable, RType>::value, void>::type Release_Resources(RType* locResource){};
146  template <typename RType> typename std::enable_if<!std::is_base_of<DResettable, RType>::value, void>::type Reset(RType* locResource){};
147 
148  //Assume that access to the shared pool won't happen very often: will mostly access the thread-local pool (this object)
149  void Get_Resources_StaticPool(void);
150  void Recycle_Resources_StaticPool(vector<DType*>& locResources);
151 
152  alignas(Get_CacheLineSize()) size_t dDebugLevel = 0;
153  alignas(Get_CacheLineSize()) size_t dGetBatchSize = 100;
154  alignas(Get_CacheLineSize()) size_t dNumToAllocateAtOnce = 20;
155  alignas(Get_CacheLineSize()) size_t dMaxLocalPoolSize = 2000;
156  alignas(Get_CacheLineSize()) vector<DType*> dResourcePool_Local;
157 
158  //static class members have external linkage: same instance shared between every translation unit (would be globally, put only private access)
159  alignas(Get_CacheLineSize()) static mutex dSharedPoolMutex;
160  alignas(Get_CacheLineSize()) static vector<DType*> dResourcePool_Shared;
161  alignas(Get_CacheLineSize()) static size_t dMaxSharedPoolSize;
162  alignas(Get_CacheLineSize()) static size_t dPoolCounter; //must be accessed within a lock due to how it's used in destructor: freeing all resources
163  alignas(Get_CacheLineSize()) static atomic<size_t> dObjectCounter; //can be accessed without a lock!
164 };
165 
166 /********************************************************************************* DSharedPtrRecycler *********************************************************************************/
167 
168 template <typename DType> class DSharedPtrRecycler
169 {
170  public:
171  DSharedPtrRecycler(void) = delete;
172  DSharedPtrRecycler(const std::shared_ptr<DResourcePool<DType>>& locResourcePool) : dResourcePool(locResourcePool) {};
173  void operator()(const DType* locResource) const{(*this)(const_cast<DType*>(locResource));}
174  void operator()(DType* locResource) const;
175 
176  private:
177  std::weak_ptr<DResourcePool<DType>> dResourcePool;
178 };
179 
180 template <typename DType> void DSharedPtrRecycler<DType>::operator()(DType* locResource) const
181 {
182  auto locSharedPtr = dResourcePool.lock();
183  if(locSharedPtr == nullptr)
184  delete locResource;
185  else
186  locSharedPtr->Recycle(locResource);
187 }
188 
189 /************************************************************************* STATIC MEMBER DEFINITIONS, STRUCTORS *************************************************************************/
190 
191 //STATIC MEMBER DEFINITIONS
192 //Since these are part of a template, these statics will only be defined once, no matter how much this header is included
193 template <typename DType> mutex DResourcePool<DType>::dSharedPoolMutex;
194 template <typename DType> vector<DType*> DResourcePool<DType>::dResourcePool_Shared = {};
195 template <typename DType> size_t DResourcePool<DType>::dMaxSharedPoolSize{1000};
196 template <typename DType> size_t DResourcePool<DType>::dPoolCounter{0};
197 template <typename DType> atomic<size_t> DResourcePool<DType>::dObjectCounter{0};
198 
199 //CONSTRUCTORS
200 template <typename DType> DResourcePool<DType>::DResourcePool(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize, size_t locMaxSharedPoolSize, size_t locDebugLevel) : DResourcePool()
201 {
202  Set_ControlParams(locGetBatchSize, locNumToAllocateAtOnce, locMaxLocalPoolSize, locMaxSharedPoolSize, locDebugLevel);
203 }
204 
205 template <typename DType> DResourcePool<DType>::DResourcePool(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize) : DResourcePool()
206 {
207  Set_ControlParams(locGetBatchSize, locNumToAllocateAtOnce, locMaxLocalPoolSize);
208 }
209 
210 template <typename DType> DResourcePool<DType>::DResourcePool(void)
211 {
212  dResourcePool_Local.reserve(dMaxLocalPoolSize);
213  {
214  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
215  ++dPoolCounter;
216  if(dDebugLevel > 0)
217  cout << "CONSTRUCTOR THREAD COUNTER " << typeid(DType).name() << ": " << dPoolCounter << endl;
218  if(dPoolCounter == 1)
219  dResourcePool_Shared.reserve(dMaxSharedPoolSize);
220  } //UNLOCK
221 }
222 
223 //DESTRUCTOR
224 template <typename DType> DResourcePool<DType>::~DResourcePool(void)
225 {
226  //Move all objects into the shared pool
227  Recycle_Resources_StaticPool(dResourcePool_Local);
228 
229  //if this was the last thread, delete all of the remaining resources
230  //first move them outside of the vector, then release the lock
231  vector<DType*> locResources;
232  {
233  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
234  --dPoolCounter;
235  if(dDebugLevel > 0)
236  cout << "DESTRUCTOR THREAD COUNTER " << typeid(DType).name() << ": " << dPoolCounter << endl;
237  if(dPoolCounter > 0)
238  return; //not the last thread
239 
240  //last thread: move all resources out of the shared pool
241  if(dDebugLevel > 0)
242  cout << "DESTRUCTOR MOVING FROM SHARED POOL " << typeid(DType).name() << ": " << std::distance(dResourcePool_Shared.begin(), dResourcePool_Shared.end()) << endl;
243  std::move(dResourcePool_Shared.begin(), dResourcePool_Shared.end(), std::back_inserter(locResources));
244  dResourcePool_Shared.clear();
245  } //UNLOCK
246 
247  //delete the resources
248  if(dDebugLevel > 0)
249  cout << "DESTRUCTOR DELETING " << typeid(DType).name() << ": " << locResources.size() << endl;
250  for(auto locResource : locResources)
251  delete locResource;
252  dObjectCounter -= locResources.size(); //I sure hope this is zero!
253  if(dDebugLevel > 0)
254  cout << "All objects (ought) to be destroyed, theoretical # remaining: " << dObjectCounter;
255 }
256 
257 /************************************************************************* NON-SHARED-POOL-ACCESSING MEMBER FUNCTIONS *************************************************************************/
258 
259 template <typename DType> DType* DResourcePool<DType>::Get_Resource(void)
260 {
261  if(dDebugLevel >= 10)
262  cout << "GET RESOURCE " << typeid(DType).name() << endl;
263  if(dResourcePool_Local.empty())
264  Get_Resources_StaticPool();
265  if(dResourcePool_Local.empty())
266  {
267  //perhaps instead use custom allocator
268  auto locPotentialNewSize = dResourcePool_Local.size() + dNumToAllocateAtOnce - 1;
269  auto locNumToAllocate = (locPotentialNewSize <= dMaxLocalPoolSize) ? dNumToAllocateAtOnce : (dMaxLocalPoolSize - dResourcePool_Local.size() + 1);
270  for(size_t loc_i = 0; loc_i < locNumToAllocate - 1; ++loc_i)
271  dResourcePool_Local.push_back(new DType);
272  dObjectCounter += locNumToAllocate;
273  return new DType();
274  }
275 
276  auto locResource = dResourcePool_Local.back();
277  dResourcePool_Local.pop_back();
278  Reset<DType>(locResource);
279  return locResource;
280 }
281 
282 template <typename DType> void DResourcePool<DType>::Set_ControlParams(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize, size_t locMaxSharedPoolSize, size_t locDebugLevel)
283 {
284  dDebugLevel = locDebugLevel;
285  dGetBatchSize = locGetBatchSize;
286  dNumToAllocateAtOnce = (locNumToAllocateAtOnce > 0) ? locNumToAllocateAtOnce : 1;
287  dMaxLocalPoolSize = locMaxLocalPoolSize;
288  {
289  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
290  dMaxSharedPoolSize = locMaxSharedPoolSize;
291  dResourcePool_Shared.reserve(dMaxSharedPoolSize);
292  } //UNLOCK
293 }
294 
295 template <typename DType> void DResourcePool<DType>::Set_ControlParams(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize)
296 {
297  dGetBatchSize = locGetBatchSize;
298  dNumToAllocateAtOnce = (locNumToAllocateAtOnce > 0) ? locNumToAllocateAtOnce : 1;
299  dMaxLocalPoolSize = locMaxLocalPoolSize;
300 }
301 
302 template <typename DType> shared_ptr<DType> DResourcePool<DType>::Get_SharedResource(void)
303 {
304  return shared_ptr<DType>(Get_Resource(), DSharedPtrRecycler<DType>(this->shared_from_this()));
305 }
306 
307 template <typename DType> void DResourcePool<DType>::Recycle(vector<const DType*>& locResources)
308 {
309  vector<DType*> locNonConstResources;
310  locNonConstResources.reserve(locResources.size());
311 
312  auto Deconstifier = [](const DType* locConstPointer) -> DType* {return const_cast<DType*>(locConstPointer);};
313  std::transform(locResources.begin(), locResources.end(), std::back_inserter(locNonConstResources), Deconstifier);
314  locResources.clear();
315 
316  Recycle(locNonConstResources);
317 }
318 
319 template <typename DType> void DResourcePool<DType>::Recycle(vector<DType*>& locResources)
320 {
321  for(auto& locResource : locResources)
322  Release_Resources<DType>(locResource);
323 
324  size_t locFirstToMoveIndex = 0;
325  auto locPotentialNewPoolSize = dResourcePool_Local.size() + locResources.size();
326  if(locPotentialNewPoolSize > dMaxLocalPoolSize) //we won't move all of the resources into the local pool, as it would be too large: only move a subset
327  locFirstToMoveIndex = locResources.size() - (dMaxLocalPoolSize - dResourcePool_Local.size());
328 
329  std::move(locResources.begin() + locFirstToMoveIndex, locResources.end(), std::back_inserter(dResourcePool_Local));
330  locResources.resize(locFirstToMoveIndex);
331  if(!locResources.empty())
332  Recycle_Resources_StaticPool(locResources);
333 }
334 
335 template <typename DType> void DResourcePool<DType>::Recycle(DType* locResource)
336 {
337  if(locResource == nullptr)
338  return;
339  vector<DType*> locResourceVector{locResource};
340  Recycle(locResourceVector);
341 }
342 
343 /************************************************************************* SHARED-POOL-ACCESSING MEMBER FUNCTIONS *************************************************************************/
344 
345 template <typename DType> void DResourcePool<DType>::Get_Resources_StaticPool(void)
346 {
347  auto locPotentialNewLocalSize = dResourcePool_Local.size() + dGetBatchSize;
348  auto locGetBatchSize = (locPotentialNewLocalSize <= dMaxLocalPoolSize) ? dGetBatchSize : dMaxLocalPoolSize - dResourcePool_Local.size();
349  {
350  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
351  if(dResourcePool_Shared.empty())
352  return;
353  auto locFirstToMoveIndex = (locGetBatchSize >= dResourcePool_Shared.size()) ? 0 : dResourcePool_Shared.size() - locGetBatchSize;
354  if(dDebugLevel > 0)
355  cout << "MOVING FROM SHARED POOL " << typeid(DType).name() << ": " << dResourcePool_Shared.size() - locFirstToMoveIndex << endl;
356  std::move(dResourcePool_Shared.begin() + locFirstToMoveIndex, dResourcePool_Shared.end(), std::back_inserter(dResourcePool_Local));
357  dResourcePool_Shared.resize(locFirstToMoveIndex);
358  } //UNLOCK
359 }
360 
361 template <typename DType> void DResourcePool<DType>::Recycle_Resources_StaticPool(vector<DType*>& locResources)
362 {
363  size_t locFirstToMoveIndex = 0;
364  {
365  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
366 
367  auto locPotentialNewPoolSize = dResourcePool_Shared.size() + locResources.size();
368  if(locPotentialNewPoolSize > dMaxSharedPoolSize) //we won't move all of the resources into the shared pool, as it would be too large: only move a subset
369  locFirstToMoveIndex = locResources.size() - (dMaxSharedPoolSize - dResourcePool_Shared.size());
370 
371  if(dDebugLevel > 0)
372  cout << "MOVING TO SHARED POOL " << typeid(DType).name() << ": " << dResourcePool_Local.size() - locFirstToMoveIndex << endl;
373 
374  std::move(locResources.begin() + locFirstToMoveIndex, locResources.end(), std::back_inserter(dResourcePool_Shared));
375  } //UNLOCK
376 
377  if(dDebugLevel > 0)
378  cout << "DELETING " << typeid(DType).name() << ": " << locFirstToMoveIndex << endl;
379 
380  //any resources that were not moved into the shared pool are deleted instead (too many)
381  auto Deleter = [](DType* locResource) -> void {delete locResource;};
382  std::for_each(locResources.begin(), locResources.begin() + locFirstToMoveIndex, Deleter);
383 
384  dObjectCounter -= locFirstToMoveIndex;
385  locResources.clear();
386 }
387 
388 template <typename DType> size_t DResourcePool<DType>::Get_SharedPoolSize(void) const
389 {
390  std::lock_guard<std::mutex> locLock(dSharedPoolMutex); //LOCK
391  return dResourcePool_Shared.size();
392 } //UNLOCK
393 
394 #endif // DResourcePool_h
static constexpr unsigned int Get_CacheLineSize(void)
void operator()(const DType *locResource) const
DSharedPtrRecycler(const std::shared_ptr< DResourcePool< DType >> &locResourcePool)
std::enable_if< std::is_base_of< DResettable, RType >::value, void >::type Reset(DResettable *locResource)
void Get_Resources_StaticPool(void)
~DResourcePool(void)
DType * Get_Resource(void)
std::enable_if<!std::is_base_of< DResettable, RType >::value, void >::type Reset(RType *locResource)
size_t Get_PoolSize(void) const
std::weak_ptr< DResourcePool< DType > > dResourcePool
size_t Get_NumObjectsAllThreads(void) const
void Recycle_Resources_StaticPool(vector< DType * > &locResources)
void Set_ControlParams(size_t locGetBatchSize, size_t locNumToAllocateAtOnce, size_t locMaxLocalPoolSize)
std::enable_if<!std::is_base_of< DResettable, RType >::value, void >::type Release_Resources(RType *locResource)
virtual void Release(void)=0
virtual void Reset(void)=0
std::enable_if< std::is_base_of< DResettable, RType >::value, void >::type Release_Resources(DResettable *locResource)
size_t Get_SharedPoolSize(void) const
shared_ptr< DType > Get_SharedResource(void)
void Recycle(const DType *locResource)