Date: 2022-03-04T17:37:10+00:00

Actual source code: objpool.hpp

      1: #ifndef PETSCOBJECTPOOL_HPP
      2: #define PETSCOBJECTPOOL_HPP

      4: #include <petscsys.h>
      5: #if !PetscDefined(HAVE_CXX_DIALECT_CXX11)
      6: #error "ObjectPool requires c++11"
      7: #endif

      9: #if defined(__cplusplus)

     11: #include <stack>
     12: #include <type_traits>

     14: #define PETSC_STATIC_ASSERT_BASE_CLASS(base_,derived_,mess_) \
     15:   static_assert(std::is_base_of<base_,derived_>::value,mess_)

     17: namespace Petsc {

     19: // Allocator ABC for interoperability with C ctors and dtors.
     20: template <typename T>
     21: class Allocator {
     22: public:
     23:   typedef T value_type;

     25:   PETSC_NODISCARD PetscErrorCode create(value_type*)  noexcept;
     26:   PETSC_NODISCARD PetscErrorCode destroy(value_type&) noexcept;
     27:   PETSC_NODISCARD PetscErrorCode reset(value_type&)   noexcept;
     28:   PETSC_NODISCARD PetscErrorCode finalize(void)       noexcept;

     30: protected:
     31:   // make the constructor protected, this forces this class to be derived from to ever be
     32:   // instantiated
     33:   Allocator() {}
     34:   ~Allocator() {}
     35: };

     37: // Default allocator that performs the bare minimum of petsc object creation and
     38: // desctruction
     39: template <typename T>
     40: class CAllocator : Allocator<T>
     41: {
     42: public:
     43:   typedef Allocator<T>                     allocator_t;
     44:   typedef typename allocator_t::value_type value_type;

     46:   PETSC_NODISCARD PetscErrorCode create(value_type *obj) const noexcept
     47:   {

     51:     PetscNew(obj);
     52:     return(0);
     53:   }

     55:   PETSC_NODISCARD PetscErrorCode destroy(value_type &obj) const noexcept
     56:   {

     60:     (*obj->ops->destroy)(obj);
     61:     PetscHeaderDestroy(&obj);
     62:     return(0);
     63:   }

     65:   PETSC_NODISCARD PetscErrorCode reset(value_type &obj) const noexcept
     66:   {

     70:     this->destroy(obj);
     71:     this->create(&obj);
     72:     return(0);
     73:   }

     75:   PETSC_NODISCARD PetscErrorCode finalize(void) const noexcept { return 0;}
     76: };

     78: // Base class to object pool, defines helpful typedefs and stores the allocator instance
     79: template <typename T, class _Allocator>
     80: class ObjectPoolBase {
     81: public:
     82:   typedef _Allocator                       allocator_t;
     83:   typedef typename allocator_t::value_type value_type;

     85: protected:
     86:   allocator_t _alloc;

     88:   PETSC_NODISCARD allocator_t& __getAllocator() noexcept { return this->_alloc;}

     90:   PETSC_NODISCARD const allocator_t& __getAllocator() const noexcept { return this->_alloc;}

     92:   // default constructor
     93:   constexpr ObjectPoolBase() noexcept(std::is_nothrow_default_constructible<allocator_t>::value) : _alloc() {}

     95:   // const copy constructor
     96:   explicit ObjectPoolBase(const allocator_t &alloc) : _alloc{alloc} {}

     98:   // move constructor
     99:   explicit ObjectPoolBase(allocator_t &&alloc) noexcept(std::is_nothrow_move_assignable<allocator_t>::value) : _alloc{std::move(alloc)} {}

    101:   ~ObjectPoolBase()
    102:   {
    103:     PETSC_STATIC_ASSERT_BASE_CLASS(Allocator<value_type>,_Allocator,"Allocator type must be subclass of Petsc::Allocator");
    104:   }
    105: };

    107: // default implementation, use the petsc c allocator
    108: template <typename T, class _Allocator = CAllocator<T>> class ObjectPool;

    110: // multi-purpose basic object-pool, useful for recirculating old "destroyed" objects. Uses
    111: // a stack to take advantage of LIFO for memory locallity. Registers all objects to be
    112: // cleaned up on PetscFinalize()
    113: template <typename T, class _Allocator>
    114: class ObjectPool : ObjectPoolBase<T,_Allocator> {
    115: protected:
    116:   typedef ObjectPoolBase<T,_Allocator> base_t;

    118: public:
    119:   typedef typename base_t::allocator_t allocator_t;
    120:   typedef typename base_t::value_type  value_type;
    121:   typedef std::stack<value_type>       stack_type;

    123: protected:
    124:   stack_type _stack;
    125:   PetscBool  _registered{PETSC_FALSE};

    127: private:
    128:   PETSC_NODISCARD static PetscErrorCode __staticFinalizer(void*) noexcept;
    129:   PETSC_NODISCARD PetscErrorCode __finalizer(void) noexcept;
    130:   PETSC_NODISCARD PetscErrorCode __registerFinalize(void) noexcept;

    132: public:
    133:   // default constructor
    134:   constexpr ObjectPool() noexcept(std::is_nothrow_default_constructible<allocator_t>::value) {}

    136:   // copy constructor
    137:   ObjectPool(ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value) : _stack{other._stack},_registered{other._registered} {}

    139:   // const copy constructor
    140:   ObjectPool(const ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value) : _stack{other._stack},_registered{other._registered} {}

    142:   // move constructor
    143:   ObjectPool(ObjectPool &&other) noexcept(std::is_nothrow_move_constructible<stack_type>::value) : _stack{std::move(other._stack)},_registered{std::move(other._registered)} {}

    145:   // copy constructor with allocator
    146:   explicit ObjectPool(const allocator_t &alloc) : base_t{alloc},_registered{PETSC_FALSE} {}

    148:   // move constructor with allocator
    149:   explicit ObjectPool(allocator_t &&alloc) noexcept(std::is_nothrow_move_constructible<allocator_t>::value) : base_t{std::move(alloc)},_registered{PETSC_FALSE} {}

    151:   // Retrieve an object from the pool, if the pool is empty a new object is created instead
    152:   PETSC_NODISCARD PetscErrorCode get(value_type&)      noexcept;
    153:   // Return an object to the pool, the object need not necessarily have been created by
    154:   // the pool
    155:   PETSC_NODISCARD PetscErrorCode reclaim(value_type&)  noexcept;
    156:   PETSC_NODISCARD PetscErrorCode reclaim(value_type&&) noexcept;

    158:   // operators
    159:   template <typename T_, class A_>
    160:   PetscBool friend operator==(const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&);

    162:   template <typename T_, class A_>
    163:   PetscBool friend operator< (const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&);
    164: };

    166: template <typename T, class _Allocator>
    167: inline PetscBool operator==(const ObjectPool<T,_Allocator> &l,const ObjectPool<T,_Allocator> &r)
    168: {
    169:   return static_cast<PetscBool>(l._stack == r._stack);
    170: }

    172: template <typename T, class _Allocator>
    173: inline PetscBool operator< (const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
    174: {
    175:   return static_cast<PetscBool>(l._stack < r._stack);
    176: }

    178: template <typename T, class _Allocator>
    179: inline PetscBool operator!=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
    180: {
    181:   return !(l._stack == r._stack);
    182: }

    184: template <typename T, class _Allocator>
    185: inline PetscBool operator> (const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
    186: {
    187:   return r._stack < l._stack;
    188: }

    190: template <typename T, class _Allocator>
    191: inline PetscBool operator>=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
    192: {
    193:   return !(l._stack < r._stack);
    194: }

    196: template <typename T, class _Allocator>
    197: inline PetscBool operator<=(const ObjectPool<T,_Allocator> &l, const ObjectPool<T,_Allocator> &r)
    198: {
    199:   return !(r._stack < l._stack);
    200: }

    202: template <typename T, class _Allocator>
    203: PetscErrorCode ObjectPool<T,_Allocator>::__staticFinalizer(void *obj) noexcept
    204: {

    208:   static_cast<ObjectPool<T,_Allocator>*>(obj)->__finalizer();
    209:   return(0);
    210: }

    212: template <typename T, class _Allocator>
    213: inline PetscErrorCode ObjectPool<T,_Allocator>::__finalizer(void) noexcept
    214: {

    218:   while (!this->_stack.empty()) {
    219:     // we do CHKERRQ __after__ the CHKERCXX on the off chance that someone uses the CXX
    220:     // error handler, we don't want to catch our own exception!
    221:     CHKERRCXX(this->__getAllocator().destroy(this->_stack.top()));
    222:     CHKERRCXX(this->_stack.pop());
    223:   }
    224:   this->__getAllocator().finalize();
    225:   this->_registered = PETSC_FALSE;
    226:   return(0);
    227: }

    229: template <typename T, class _Allocator>
    230: inline PetscErrorCode ObjectPool<T,_Allocator>::__registerFinalize(void) noexcept
    231: {
    233:   if (PetscUnlikely(!this->_registered)) {
    235:     PetscContainer contain;

    237:     /* use a PetscContainer as a form of thunk, it holds not only a pointer to this but
    238:        also the pointer to the static member function, which just converts the thunk back
    239:        to this. none of this would be needed if PetscRegisterFinalize() just took a void*
    240:        itself though...  */
    241:     PetscContainerCreate(PETSC_COMM_SELF,&contain);
    242:     PetscContainerSetPointer(contain,this);
    243:     PetscContainerSetUserDestroy(contain,this->__staticFinalizer);
    244:     PetscObjectRegisterDestroy((PetscObject)contain);
    245:     this->_registered = PETSC_TRUE;
    246:   }
    247:   return(0);
    248: }

    250: template <typename T, class _Allocator>
    251: inline PetscErrorCode ObjectPool<T,_Allocator>::get(value_type &obj) noexcept
    252: {

    256:   this->__registerFinalize();
    257:   if (this->_stack.empty()) {
    258:     this->__getAllocator().create(&obj);
    259:   } else {
    260:     CHKERRCXX(obj = std::move(this->_stack.top()));
    261:     CHKERRCXX(this->_stack.pop());
    262:   }
    263:   return(0);
    264: }

    266: template <typename T, class _Allocator>
    267: inline PetscErrorCode ObjectPool<T,_Allocator>::reclaim(value_type &obj) noexcept
    268: {

    272:   if (PetscUnlikely(!this->_registered)) {
    273:     /* this is necessary if an object is "reclaimed" within another PetscFinalize()
    274:        registered cleanup after this object pool has returned from it's finalizer. In
    275:        this case, instead of pushing onto the stack we just destroy the object directly */
    276:     this->__getAllocator().destroy(obj);
    277:   } else {
    278:     this->__getAllocator().reset(obj);
    279:     CHKERRCXX(this->_stack.push(std::move(obj)));
    280:     obj = nullptr;
    281:   }
    282:   return(0);
    283: }

    285: template <typename T, class _Allocator>
    286: inline PetscErrorCode ObjectPool<T,_Allocator>::reclaim(value_type &&obj) noexcept
    287: {

    291:   if (PetscUnlikely(!this->_registered)) {
    292:     /* this is necessary if an object is "reclaimed" within another PetscFinalize()
    293:        registered cleanup after this object pool has returned from it's finalizer. In
    294:        this case, instead of pushing onto the stack we just destroy the object directly */
    295:     this->__getAllocator().destroy(obj);
    296:   } else {
    297:     // allows const allocator_t& to be used if allocator defines a const reset
    298:     this->__getAllocator().reset(obj);
    299:     CHKERRCXX(this->_stack.push(std::move(obj)));
    300:     obj = nullptr;
    301:   }
    302:   return(0);
    303: }

    305: } // namespace Petsc

    307: #endif /* __cplusplus */

    309: #endif /* PETSCOBJECTPOOL_HPP */