BulkAllocator.h
Go to the documentation of this file.
1 /**
2  * @file BulkAllocator.h
3  * @brief Memory allocator for large amount of (small) objects
4  * @author Gianluca Petrillo (petrillo@fnal.gov)
5  * @date August 17th, 2014
6  */
7 
8 
9 //--- BEGIN issue #19494 -------------------------------------------------------
10 /// @bug BulkAllocator.h is currently broken; see issue #19494.
11 // We are leaving it here because, being a header, it will not bother unless
12 // explicitly invoked. Note that there is a unit test for it too.
13 #error ("BulkAllocator.h is currently broken; see issue #19494.")
14 //--- END issue #19494 ---------------------------------------------------------
15 
16 
17 #ifndef BULKALLOCATOR_H
18 #define BULKALLOCATOR_H
19 
20 // interface include
21 #include <memory> // std::allocator<>, std::unique_ptr<>
22 #include <stdexcept> // std::logic_error
23 #include <cstdlib> // std::free
24 
25 namespace lar {
26  /// Namespace hiding implementation details
27  namespace details {
28  /// Namespace specific to bulk allocator
29  namespace bulk_allocator {
30  template <typename T>
32  } // namespace bulk_allocator
33  } // namespace details
34 
35 
36  /// Exception thrown when BulkAllocator-specific allocation errors happen
37  class memory_error: public std::bad_alloc {
38  public:
39  memory_error(): std::bad_alloc() {}
40  memory_error(const char* message): std::bad_alloc(), msg(message) {}
41 
42  virtual const char* what() const throw() override { return msg; }
43 
44  private:
45  const char* msg = nullptr;
46  }; // class memory_error
47 
48  /**
49  * @brief Aggressive allocator reserving a lot of memory in advance
50  * @param T type being allocated
51  *
52  * This allocator appropriates memory in large chunks of GetChunkSize()
53  * elements of type T. The memory will never be deleted! (but read further)
54  *
55  * @note With C++17, an allocator called `std::pmr::monotonic_buffer_resource`
56  * is available that seems to have pretty much the same functionality as
57  * this one (but STL quality).
58  * When C++17 is adopted by LArSoft (and the supported compilers have a
59  * complete enough support for it), the users of `BulkAllocator` should
60  * migrate to that one. Note that the interface is different, and
61  * probably the way to use it is also different.
62  *
63  * <h3>Deletion policy</h3>
64  *
65  * This allocator does not release not reuse deallocated memory. This design
66  * choice is meant to reflect a specific use case where a large amount of
67  * elements is created and then used, and the created object is fairly static.
68  * Tracking freed memory fragments takes time and more memory, and reusing
69  * them too.
70  * Nevertheless, the allocator has a user count; when no user is present,
71  * all the memory is deallocated. This can be convenient, or disastrous:
72  * remember that the elements of a container can (or just might) not survive
73  * after the container is destroyed. Clearing the container will not trigger
74  * this self-distruction; if you are completely sure that no other container
75  * is currently using the same allocator, you can explicitly Free() its
76  * memory.
77  *
78  * <h3>One allocator for them all</h3>
79  *
80  * Since STL containers do not necessarely store their allocator but they
81  * may create it with a default constructor, allocators should be formally
82  * stateless, and every newly-created allocator should be equivalent
83  * (or else a copy of an allocator will not know what the original has
84  * allocated already).
85  *
86  * This is implemented hiding a singleton in the allocator (as a static
87  * member). Each allocator type has its own singleton, i.e., a
88  * BulkAllocator<int> does not share memory with a BulkAllocator<double>,
89  * but all BulkAllocator<int> share.
90  */
91  template <typename T>
92  class BulkAllocator: public std::allocator<T> {
93  public:
94  using BaseAllocator_t = std::allocator<T>;
95 
96  // import types from the STL allocator
97  typedef typename BaseAllocator_t::size_type size_type;
98  typedef typename BaseAllocator_t::difference_type difference_type;
99 
100  typedef typename BaseAllocator_t::value_type value_type;
101 
102  typedef typename BaseAllocator_t::pointer pointer;
103  typedef typename BaseAllocator_t::const_pointer const_pointer;
104 
105  typedef typename BaseAllocator_t::reference reference;
106  typedef typename BaseAllocator_t::const_reference const_reference;
107 
108  template<typename U>
109  struct rebind {
111  };
112 
113  /// Default constructor: uses the default chunk size
114  BulkAllocator() noexcept: BulkAllocator(GetChunkSize(), false) {}
115 
116  /// Constructor: sets chunk size and optionally allocates the first chunk
117  BulkAllocator(size_type ChunkSize, bool bPreallocate = false) noexcept
118  { CreateGlobalAllocator(ChunkSize, bPreallocate); }
119 
120  /// Copy constructor: default
121  BulkAllocator(const BulkAllocator &a) noexcept = default;
122 
123  /// Move constructor: default
124  BulkAllocator(BulkAllocator &&a) noexcept = default;
125 
126  /// General copy constructor; currently, it does not preallocate
127  template <class U>
128  BulkAllocator(const BulkAllocator<U> &a) noexcept:
129  BaseAllocator_t(a) { SetChunkSize(a.GetChunkSize()); }
130 
131  /// Copy assignment: default
132  BulkAllocator& operator = (const BulkAllocator &a) = default;
133 
134  /// Move assignment: default
135  BulkAllocator& operator = (BulkAllocator &&a) = default;
136 
137  /// Destructor; memory is freed only if no allocators are left around
138  ~BulkAllocator() { GlobalAllocator.RemoveUser(); }
139 
140  /// Allocates memory for n elements
141  pointer allocate(size_type n, const void* /* hint */ = 0);
142 
143  /// Frees n elements at p
144  void deallocate(pointer p, size_type n);
145 
146  /// Releases all the allocated memory: dangerous!
147  static void Free() { GlobalAllocator.Free(); }
148 
149  /// Returns the chunk size of the underlying global allocator
150  static size_type GetChunkSize() { return GlobalAllocator.GetChunkSize(); }
151 
152  /// Sets chunk size of global allocator; only affects future allocations!
153  static void SetChunkSize(size_type ChunkSize)
154  { GlobalAllocator.SetChunkSize(ChunkSize); }
155 
156  private:
158  SharedAllocator_t; ///< shared allocator type
159 
160  /// Makes sure we have a valid "global allocator"
161  void CreateGlobalAllocator(size_type ChunkSize, bool bPreallocate = false);
162 
163  /// The allocator shared by all instances of this object
165 
166 
167  }; // class BulkAllocator<>
168 
169 } // namespace lar
170 
171 
172 //------------------------------------------------------------------------------
173 #include <algorithm> // std::max()
174 #include <vector>
175 #include <iostream>
176 #include <array>
177 #include <typeinfo>
178 #include <string>
179 #ifdef __GNUG__
180 # include <cxxabi.h>
181 #endif // __GNUG__
182 
183 namespace lar {
184  namespace details {
185  ///@{
186  /**
187  * @brief Demangles the name of a type
188  * @tparam T type to be demangled
189  * @param [anonymous] parameter to determine the type
190  * @return a string with the demangled name
191  *
192  * This function relies on GCC ABI; if there is no GCC, no demangling
193  * happens.
194  * One version of this function takes no parameters, and the type must be
195  * specified explicitly in the call. The other takes one parameter, that
196  * is not actually used but allows the compiler to understand which type
197  * to use. The following usese are equivalent:
198  * @code
199  * std::vector<int> v;
200  * std::cout << demangle<std::vector<int>>() << std::endl;
201  * std::cout << demangle(v) << std::endl;
202  * @endcode
203  */
204  template <typename T>
206  std::string name = typeid(T).name();
207  #ifdef __GNUG__
208  int status; // some arbitrary value to eliminate the compiler warning
209  std::unique_ptr<char, void(*)(void*)> res
210  { abi::__cxa_demangle(name.c_str(), NULL, NULL, &status), std::free };
211  return (status==0) ? res.get() : name ;
212  #else // !__GNUG__
213  return name;
214  #endif // ?__GNUG__
215  } // demangle()
216 
217  template <typename T>
218  inline std::string demangle(const T&) { return demangle<T>(); }
219  ///@}
220 
221  namespace bulk_allocator {
222  constexpr bool bDebug = false;
223 
224  /// A simple reference counter, keep track of a number of users.
226  public:
227  typedef unsigned int Counter_t; ///< type of user counter
228 
229  /// Returns whether there are currently users
230  bool hasUsers() const { return counter > 0; }
231 
232  /// Returns the number of registered users
233  Counter_t Count() const { return counter; }
234 
235  /// Adds a user to the users count
236  void AddUser() { ++counter; }
237 
238  /// Removed a user to the users count; returns false if no user yet
239  bool RemoveUser()
240  { if (!counter) return false; --counter; return true; }
241 
242  private:
243  Counter_t counter = 0;
244  }; // class ReferenceCounter
245 
246 
247  /**
248  * @brief A class managing a memory pool
249  *
250  * The management policy is to allocate *big* chunks of memory.
251  * Memory is never freed, until the last user is removed (which is
252  * responsibility of the caller), this object is destroyed of Free() is
253  * explicitly called.
254  * The amount of memory on
255  *
256  * This class has a users counter. The count must be explicitly handled by
257  * the caller.
258  */
259  template <typename T>
260  class BulkAllocatorBase: public ReferenceCounter {
261  public:
262  typedef size_t size_type;
263  typedef T value_type;
264  typedef T* pointer;
265 
266  /// Constructor; preallocates memory if explicitly requested
268  size_type NewChunkSize = DefaultChunkSize, bool bPreallocate = false
269  );
270 
271  /// Destructor: releases all the memory pool (@see Free())
272  ~BulkAllocatorBase() { Free(); }
273 
274  /// Releases the pool of memory; all pointer to it become invalid
275  void Free();
276 
277  /// Returns a pointer to memory for n new values of type T
278  pointer Get(size_type n);
279 
280  /// Releases memory pointed by the specified pointer (but it does not).
281  void Release(pointer) {}
282 
283  /// Add a new pool user with the current parameters
285 
286  /// Add a new pool user with new parameters
287  void AddUser(size_type NewChunkSize, bool bPreallocate = false);
288 
289  /// Removed a user to the users count; if no user is left, free the pool
290  bool RemoveUser();
291 
292  /// Returns the total number of entries in the pool
293  size_type AllocatedCount() const;
294 
295  /// Returns the total number of used entries in the pool
296  size_type UsedCount() const;
297 
298  /// Returns the total number of unused entries in the pool
299  size_type FreeCount() const;
300 
301  /// Returns the number of memory pool chunks allocated
302  size_type NChunks() const { return MemoryPool.size(); }
303 
304  /// Returns an array equivalent to { UsedCount(), FreeCount() }
305  std::array<size_type, 2> GetCounts() const;
306 
307  /// Sets the chunk size for the future allocations
308  void SetChunkSize(size_type NewChunkSize, bool force = false);
309 
310  /// Returns the current chunk size
311  size_type GetChunkSize() const { return ChunkSize; }
312 
313  /// Preallocates a chunk of the current ChunkSize;
314  /// @see Preallocate(size_type)
315  void Preallocate() { Preallocate(ChunkSize); }
316 
317  private:
318  typedef std::allocator<T> Allocator_t;
319  typedef typename Allocator_t::difference_type difference_type;
320 
321  Allocator_t allocator; ///< the actual allocator we use
322 
323  /// Internal memory chunk; like a std::vector, but does not construct
325  public:
326  Allocator_t* allocator; ///< reference to the allocator to be used
327 
328  pointer begin = nullptr; ///< start of the pool
329  pointer end = nullptr; ///< end of the pool
330  pointer free = nullptr; ///< first unused element of the pool
331 
332  ///< Constructor: allocates memory
333  MemoryChunk_t(Allocator_t& alloc, size_type n): allocator(&alloc)
334  {
335  begin = n? allocator->allocate(n): nullptr;
336  end = begin + n;
337  free = begin;
338  } // MemoryChunk_t()
339  MemoryChunk_t(const MemoryChunk_t&) = delete; ///< Can't copy
340  MemoryChunk_t(MemoryChunk_t&&); ///< Move constructor
341 
342  ~MemoryChunk_t() { allocator->deallocate(begin, size()); }
343 
344  MemoryChunk_t& operator=(const MemoryChunk_t&) = delete;
345  ///< Can't assign
346  MemoryChunk_t& operator=(MemoryChunk_t&&); ///< Move assignment
347 
348  /// Returns the number of elements in this pool
349  size_type size() const { return end - begin; }
350 
351  /// Returns the number of free elements in this pool
352  size_type available() const { return end - free; }
353 
354  /// Returns the number of used elements in this pool
355  size_type used() const { return free - begin; }
356 
357  /// Returns whether the chunk is full
358  bool full() const { return !available(); }
359 
360  /// Returns a pointer to a free item, or nullptr if none is available
361  pointer get() { return (free != end)? free++: nullptr; }
362 
363  /// Returns a pointer to n free items, or nullptr if not available
364  pointer get(size_t n)
365  {
366  pointer ptr = free;
367  if ((free += n) <= end) return ptr;
368  free = ptr;
369  return nullptr;
370  }
371 
372  private:
373  ///< Default constructor (does nothing)
374  MemoryChunk_t(): allocator(nullptr) {}
375 
376  }; // class MemoryChunk_t
377 
378  typedef std::vector<MemoryChunk_t> MemoryPool_t;
379 
380  size_type ChunkSize; ///< size of the chunks to add
381  MemoryPool_t MemoryPool; ///< list of all memory chunks; first is free
382 
383  /// Default chunk size (default: 10000)
384  static size_type DefaultChunkSize;
385 
386  /// Preallocates a chunk of the given size; allocates if free space < n
387  void Preallocate(size_type n);
388 
389  }; // class BulkAllocatorBase<>
390 
391 
392  template <typename T>
394  std::swap(allocator, from.allocator);
395  std::swap(begin, from.begin);
396  std::swap(end, from.end);
397  std::swap(free, from.free);
398  } // BulkAllocatorBase<T>::MemoryChunk_t::MemoryChunk_t(MemoryChunk_t&&)
399 
400  template <typename T>
403  {
404  std::swap(allocator, from.allocator);
405  std::swap(begin, from.begin);
406  std::swap(end, from.end);
407  std::swap(free, from.free);
408  return *this;
409  } // BulkAllocatorBase<T>::MemoryChunk_t::operator= (MemoryChunk_t&&)
410 
411 
412  template <typename T>
415 
416  template <typename T>
418  (size_type NewChunkSize, bool bPreallocate /* = false */):
419  ChunkSize(NewChunkSize), MemoryPool()
420  {
421  Preallocate(bPreallocate? ChunkSize: 0);
422  if (bDebug) {
423  std::cout << "BulkAllocatorBase[" << ((void*) this)
424  << "] created for type " << demangle<value_type>()
425  << " with chunk size " << GetChunkSize()
426  << " x" << sizeof(value_type) << " byte => "
427  << (GetChunkSize()*sizeof(value_type)) << " bytes/chunk"
428  << std::endl;
429  } // if debug
430  } // BulkAllocatorBase<T>::BulkAllocatorBase()
431 
432 
433  template <typename T>
435  if (bDebug) {
436  std::cout << "BulkAllocatorBase[" << ((void*) this) << "] freeing "
437  << NChunks() << " memory chunks with " << AllocatedCount()
438  << " elements" << std::endl;
439  } // if debug
440  MemoryPool.clear();
441  } // BulkAllocatorBase<T>::Free()
442 
443 
444  template <typename T>
447  if (hasUsers()) return true;
448  Free();
449  return false;
450  } // BulkAllocatorBase<T>::RemoveUser()
451 
452 
453  template <typename T>
455  (size_type NewChunkSize, bool bPreallocate /* = false */)
456  {
457  AddUser();
458  SetChunkSize(NewChunkSize);
459  Preallocate(bPreallocate? ChunkSize: 0);
460  } // BulkAllocatorBase<T>::AddUser(size_type, bool )
461 
462 
463  template <typename T>
465  if (MemoryPool.empty() || (MemoryPool.front().available() < n))
466  MemoryPool.emplace_back(allocator, n);
467  } // BulkAllocatorBase<T>::RemoveUser()
468 
469 
470  template <typename T>
473  {
474  size_type n = 0;
475  for (const auto& chunk: MemoryPool) n += chunk.size();
476  return n;
477  } // AllocatedCount()
478 
479 
480  template <typename T>
483  {
484  size_type n = 0;
485  for (const auto& chunk: MemoryPool) n += chunk.used();
486  return n;
487  } // BulkAllocatorBase<T>::UsedCount()
488 
489 
490  template <typename T>
491  std::array<typename BulkAllocatorBase<T>::size_type, 2>
493  {
494  // BUG the double brace syntax is required to work around clang bug 21629
495  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
496  std::array<BulkAllocatorBase<T>::size_type, 2> stats = {{ 0U, 0U }};
497  for (const auto& chunk: MemoryPool) {
498  stats[0] += chunk.used();
499  stats[1] += chunk.available();
500  } // for
501  return stats;
502  } // BulkAllocatorBase<T>::GetCounts()
503 
504 
505  template <typename T>
507  (size_type NewChunkSize, bool force /* = false */)
508  {
509  if ((GetChunkSize() == NewChunkSize) && !force) return;
510  if (bDebug) {
511  std::cout << "BulkAllocatorBase[" << ((void*) this) << "]"
512  << " changing chunk size: " << GetChunkSize() << " => "
513  << NewChunkSize << ": x" << sizeof(value_type) << " byte => "
514  << (NewChunkSize*sizeof(value_type)) << " bytes/chunk"
515  << std::endl;
516  }
517  ChunkSize = NewChunkSize;
518  } // BulkAllocatorBase<T>::SetChunkSize()
519 
520 
521  template <typename T>
524  {
525  if (n == 0) return nullptr;
526  // get the free pointer from the latest chunk (#0)
527  pointer ptr = MemoryPool.front().get(n);
528  if (ptr) return ptr;
529  // no free element left in that chunk:
530  // - create a new one in the first position of the pool (move the rest)
531  // - return the pointer from the new pool
532  // (if it fails, it fails... but how could that happen?)
533  if (bDebug) {
534  std::array<size_type, 2> stats = GetCounts();
535  std::cout << "BulkAllocatorBase[" << ((void*) this)
536  << "] allocating " << std::max(ChunkSize, n)
537  << " more elements (on top of the current " << (stats[0] + stats[1])
538  << " elements, " << stats[1] << " unused)" << std::endl;
539  } // if debug
540  return MemoryPool.emplace
541  (MemoryPool.begin(), allocator, std::max(ChunkSize, n))->get(n);
542  } // BulkAllocatorBase<T>::Get()
543 
544 
545  } // namespace bulk_allocator
546  } // namespace details
547 
548 
549  template <typename T>
552 
553  template <typename T>
555  (size_type ChunkSize, bool bPreallocate /* = false */)
556  {
557  GlobalAllocator.AddUser(ChunkSize, bPreallocate);
558  } // BulkAllocator<T>::CreateGlobalAllocator()
559 
560  template <typename T>
562  (size_type n, const void * /* hint = 0 */)
563  { return GlobalAllocator.Get(n); }
564 
565  template <typename T>
567  return GlobalAllocator.Release(p);
568  } // BulkAllocator<T>::deallocate()
569 } // namespace lar
570 
571 
572 #endif // BULKALLOCATOR_H
573 
A class managing a memory pool.
Definition: BulkAllocator.h:31
static QCString name
Definition: declinfo.cpp:673
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
BaseAllocator_t::pointer pointer
MemoryChunk_t & operator=(const MemoryChunk_t &)=delete
Can&#39;t assign.
void Release(pointer)
Releases memory pointed by the specified pointer (but it does not).
Internal memory chunk; like a std::vector, but does not construct.
BaseAllocator_t::difference_type difference_type
Definition: BulkAllocator.h:98
BulkAllocatorBase(size_type NewChunkSize=DefaultChunkSize, bool bPreallocate=false)
Constructor; preallocates memory if explicitly requested.
bool RemoveUser()
Removed a user to the users count; returns false if no user yet.
void msg(const char *fmt,...)
Definition: message.cpp:107
void deallocate(pointer p, size_type n)
Frees n elements at p.
void CreateGlobalAllocator(size_type ChunkSize, bool bPreallocate=false)
Makes sure we have a valid "global allocator".
Counter_t Count() const
Returns the number of registered users.
pointer Get(size_type n)
Returns a pointer to memory for n new values of type T.
std::array< size_type, 2 > GetCounts() const
Returns an array equivalent to { UsedCount(), FreeCount() }.
std::string string
Definition: nybbler.cc:12
void AddUser()
Adds a user to the users count.
Allocator_t::difference_type difference_type
void Free()
Releases the pool of memory; all pointer to it become invalid.
memory_error(const char *message)
Definition: BulkAllocator.h:40
STL namespace.
static void Free()
Releases all the allocated memory: dangerous!
static SharedAllocator_t GlobalAllocator
The allocator shared by all instances of this object.
BaseAllocator_t::reference reference
~BulkAllocator()
Destructor; memory is freed only if no allocators are left around.
bool full() const
Returns whether the chunk is full.
Exception thrown when BulkAllocator-specific allocation errors happen.
Definition: BulkAllocator.h:37
size_type ChunkSize
size of the chunks to add
BulkAllocator() noexcept
Default constructor: uses the default chunk size.
size_type AllocatedCount() const
Returns the total number of entries in the pool.
BaseAllocator_t::size_type size_type
Definition: BulkAllocator.h:97
BulkAllocator< U > other
BulkAllocator(const BulkAllocator< U > &a) noexcept
General copy constructor; currently, it does not preallocate.
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
Allocator_t * allocator
reference to the allocator to be used
A simple reference counter, keep track of a number of users.
BaseAllocator_t::const_reference const_reference
pointer allocate(size_type n, const void *=0)
Allocates memory for n elements.
void swap(Handle< T > &a, Handle< T > &b)
BulkAllocator(size_type ChunkSize, bool bPreallocate=false) noexcept
Constructor: sets chunk size and optionally allocates the first chunk.
std::void_t< T > n
const double a
virtual const char * what() const override
Definition: BulkAllocator.h:42
~BulkAllocatorBase()
Destructor: releases all the memory pool (.
size_type size() const
Returns the number of elements in this pool.
details::bulk_allocator::BulkAllocatorBase< T > SharedAllocator_t
shared allocator type
p
Definition: test.py:223
Allocator_t allocator
the actual allocator we use
Aggressive allocator reserving a lot of memory in advance.
Definition: BulkAllocator.h:92
std::string demangle()
Demangles the name of a type.
static int max(int a, int b)
size_type NChunks() const
Returns the number of memory pool chunks allocated.
static size_type DefaultChunkSize
Default chunk size (default: 10000)
size_type available() const
Returns the number of free elements in this pool.
size_type UsedCount() const
Returns the total number of used entries in the pool.
MemoryChunk_t()
< Default constructor (does nothing)
size_type GetChunkSize() const
Returns the current chunk size.
bool hasUsers() const
Returns whether there are currently users.
unsigned int Counter_t
type of user counter
LArSoft-specific namespace.
static void SetChunkSize(size_type ChunkSize)
Sets chunk size of global allocator; only affects future allocations!
BaseAllocator_t::value_type value_type
BaseAllocator_t::const_pointer const_pointer
void SetChunkSize(size_type NewChunkSize, bool force=false)
Sets the chunk size for the future allocations.
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:72
std::allocator< T > BaseAllocator_t
Definition: BulkAllocator.h:94
size_type used() const
Returns the number of used elements in this pool.
static size_type GetChunkSize()
Returns the chunk size of the underlying global allocator.
bool RemoveUser()
Removed a user to the users count; if no user is left, free the pool.
MemoryPool_t MemoryPool
list of all memory chunks; first is free
void AddUser()
Add a new pool user with the current parameters.
QTextStream & endl(QTextStream &s)