Actual source code: objpool.hpp

  1: #ifndef PETSCOBJECTPOOL_HPP
  2: #define PETSCOBJECTPOOL_HPP

  4: #include <petscsys.h>

  6: #if defined(__cplusplus)

  8: #include <stack>
  9: #include <type_traits>

 11: namespace Petsc
 12: {

 14: // Allocator ABC for interoperability with C ctors and dtors.
 15: template <typename T>
 16: class AllocatorBase
 17: {
 18: public:
 19:   using value_type = T;

 21:   PETSC_NODISCARD PetscErrorCode create(value_type*)  noexcept;
 22:   PETSC_NODISCARD PetscErrorCode destroy(value_type&) noexcept;
 23:   PETSC_NODISCARD PetscErrorCode reset(value_type&)   noexcept;
 24:   PETSC_NODISCARD PetscErrorCode finalize()           noexcept;

 26: protected:
 27:   // make the constructor protected, this forces this class to be derived from to ever be
 28:   // instantiated
 29:   AllocatorBase() noexcept = default;
 30: };

 32: // Default allocator that performs the bare minimum of petsc object creation and
 33: // desctruction
 34: template <typename T>
 35: class CAllocator : public AllocatorBase<T>
 36: {
 37: public:
 38:   using allocator_type = AllocatorBase<T>;
 39:   using value_type     = typename allocator_type::value_type;

 41:   PETSC_NODISCARD PetscErrorCode create(value_type *obj) const noexcept
 42:   {
 43:     PetscNew(obj);
 44:     return 0;
 45:   }

 47:   PETSC_NODISCARD PetscErrorCode destroy(value_type &obj) const noexcept
 48:   {
 49:     (*obj->ops->destroy)(obj);
 50:     PetscHeaderDestroy(&obj);
 51:     return 0;
 52:   }

 54:   PETSC_NODISCARD PetscErrorCode reset(value_type &obj) const noexcept
 55:   {
 56:     this->destroy(obj);
 57:     this->create(&obj);
 58:     return 0;
 59:   }

 61:   PETSC_NODISCARD PetscErrorCode finalize() const noexcept { return 0; }
 62: };

 64: namespace detail
 65: {

 67: // Base class to object pool, defines helpful typedefs and stores the allocator instance
 68: template <typename T, class Allocator>
 69: class ObjectPoolBase
 70: {
 71: public:
 72:   using allocator_type = Allocator;
 73:   using value_type     = typename allocator_type::value_type;

 75: protected:
 76:   allocator_type alloc_;

 78:   PETSC_NODISCARD       allocator_type&  allocator()       noexcept { return alloc_; }
 79:   PETSC_NODISCARD const allocator_type& callocator() const noexcept { return alloc_; }

 81:   // default constructor
 82:   constexpr ObjectPoolBase() noexcept(std::is_nothrow_default_constructible<allocator_type>::value)
 83:     : alloc_()
 84:   { }

 86:   // const copy constructor
 87:   explicit ObjectPoolBase(const allocator_type &alloc) : alloc_(alloc) { }

 89:   // move constructor
 90:   explicit ObjectPoolBase(allocator_type &&alloc)
 91:     noexcept(std::is_nothrow_move_assignable<allocator_type>::value)
 92:     : alloc_(std::move(alloc))
 93:   { }

 95:   static_assert(std::is_base_of<AllocatorBase<value_type>,Allocator>::value,"");
 96: };

 98: } // namespace detail

100: // default implementation, use the petsc c allocator
101: template <typename T, class Allocator = CAllocator<T>> class ObjectPool;

103: // multi-purpose basic object-pool, useful for recirculating old "destroyed" objects. Uses
104: // a stack to take advantage of LIFO for memory locallity. Registers all objects to be
105: // cleaned up on PetscFinalize()
106: template <typename T, class Allocator>
107: class ObjectPool : detail::ObjectPoolBase<T,Allocator>
108: {
109: protected:
110:   using base_type = detail::ObjectPoolBase<T,Allocator>;

112: public:
113:   using allocator_type = typename base_type::allocator_type;
114:   using value_type     = typename base_type::value_type;
115:   using stack_type     = std::stack<value_type>;
116:   using base_type::allocator;
117:   using base_type::callocator;

119: private:
120:   stack_type stack_;
121:   bool       registered_ = false;

123:   PETSC_NODISCARD        PetscErrorCode registerFinalize_()     noexcept;
124:   PETSC_NODISCARD        PetscErrorCode finalizer_()            noexcept;
125:   PETSC_NODISCARD static PetscErrorCode staticFinalizer_(void*) noexcept;

127: public:
128:   // default constructor
129:   constexpr ObjectPool() noexcept(std::is_nothrow_default_constructible<allocator_type>::value)
130:     : stack_()
131:   { }

133:   // destructor
134:   ~ObjectPool() noexcept
135:   {
136:     PETSC_COMM_SELF,finalizer_();
137:   }

139:   // copy constructor
140:   ObjectPool(ObjectPool &other) noexcept(std::is_nothrow_copy_constructible<stack_type>::value)
141:     : stack_(other.stack_),registered_(other.registered_)
142:   { }

144:   // const copy constructor
145:   ObjectPool(const ObjectPool &other)
146:     noexcept(std::is_nothrow_copy_constructible<stack_type>::value)
147:     : stack_(other.stack_),registered_(other.registered_)
148:   { }

150:   // move constructor
151:   ObjectPool(ObjectPool &&other) noexcept(std::is_nothrow_move_constructible<stack_type>::value)
152:     : stack_(std::move(other.stack_)),registered_(std::move(other.registered_))
153:   { }

155:   // copy constructor with allocator
156:   explicit ObjectPool(const allocator_type &alloc) : base_type(alloc) { }

158:   // move constructor with allocator
159:   explicit ObjectPool(allocator_type &&alloc)
160:     noexcept(std::is_nothrow_move_constructible<allocator_type>::value)
161:     : base_type(std::move(alloc))
162:   { }

164:   // Retrieve an object from the pool, if the pool is empty a new object is created instead
165:   PETSC_NODISCARD PetscErrorCode get(value_type&)      noexcept;

167:   // Return an object to the pool, the object need not necessarily have been created by
168:   // the pool, note this only accepts r-value references. The pool takes ownership of all
169:   // managed objects.
170:   PETSC_NODISCARD PetscErrorCode reclaim(value_type&&) noexcept;

172:   // operators
173:   template <typename T_, class A_>
174:   PetscBool friend operator==(const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&) noexcept;

176:   template <typename T_, class A_>
177:   PetscBool friend operator< (const ObjectPool<T_,A_>&,const ObjectPool<T_,A_>&) noexcept;
178: };

180: template <typename T, class Allocator>
181: inline PetscBool operator==(const ObjectPool<T,Allocator> &l,const ObjectPool<T,Allocator> &r) noexcept
182: {
183:   return static_cast<PetscBool>(l.stack_ == r.stack_);
184: }

186: template <typename T, class Allocator>
187: inline PetscBool operator< (const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
188: {
189:   return static_cast<PetscBool>(l.stack_ < r.stack_);
190: }

192: template <typename T, class Allocator>
193: inline PetscBool operator!=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
194: {
195:   return !(l.stack_ == r.stack_);
196: }

198: template <typename T, class Allocator>
199: inline PetscBool operator> (const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
200: {
201:   return r.stack_ < l.stack_;
202: }

204: template <typename T, class Allocator>
205: inline PetscBool operator>=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
206: {
207:   return !(l.stack_ < r.stack_);
208: }

210: template <typename T, class Allocator>
211: inline PetscBool operator<=(const ObjectPool<T,Allocator> &l, const ObjectPool<T,Allocator> &r) noexcept
212: {
213:   return !(r.stack_ < l.stack_);
214: }

216: template <typename T, class Allocator>
217: inline PetscErrorCode ObjectPool<T,Allocator>::finalizer_() noexcept
218: {
219:   while (!stack_.empty()) {
220:     // we do CHKERRQ __after__ the CHKERCXX on the off chance that someone uses the CXX
221:     // error handler, we don't want to catch our own exception!
222:     PetscCall(this->allocator().destroy(stack_.top()));
223:     stack_.pop();
224:   }
225:   this->allocator().finalize();
226:   registered_ = false;
227:   return 0;
228: }

230: template <typename T, class Allocator>
231: inline PetscErrorCode ObjectPool<T,Allocator>::staticFinalizer_(void *obj) noexcept
232: {
233:   static_cast<ObjectPool<T,Allocator>*>(obj)->finalizer_();
234:   return 0;
235: }

237: template <typename T, class Allocator>
238: inline PetscErrorCode ObjectPool<T,Allocator>::registerFinalize_() noexcept
239: {
240:   PetscContainer contain;

242:   if (PetscLikely(registered_)) return 0;
243:   /* use a PetscContainer as a form of thunk, it holds not only a pointer to this but
244:      also the pointer to the static member function, which just converts the thunk back
245:      to this. none of this would be needed if PetscRegisterFinalize() just took a void*
246:      itself though...  */
247:   PetscContainerCreate(PETSC_COMM_SELF,&contain);
248:   PetscContainerSetPointer(contain,this);
249:   PetscContainerSetUserDestroy(contain,staticFinalizer_);
250:   PetscObjectRegisterDestroy(reinterpret_cast<PetscObject>(contain));
251:   registered_ = true;
252:   return 0;
253: }

255: template <typename T, class Allocator>
256: inline PetscErrorCode ObjectPool<T,Allocator>::get(value_type &obj) noexcept
257: {
258:   registerFinalize_();
259:   if (stack_.empty()) {
260:     this->allocator().create(&obj);
261:   } else {
262:     obj = std::move(stack_.top());
263:     stack_.pop();
264:   }
265:   return 0;
266: }

268: template <typename T, class Allocator>
269: inline PetscErrorCode ObjectPool<T,Allocator>::reclaim(value_type &&obj) noexcept
270: {
271:   if (PetscLikely(registered_)) {
272:     // allows const allocator_t& to be used if allocator defines a const reset
273:     this->allocator().reset(obj);
274:     stack_.push(std::move(obj));
275:   } else {
276:     // this is necessary if an object is "reclaimed" within another PetscFinalize() registered
277:     // cleanup after this object pool has returned from it's finalizer. In this case, instead
278:     // of pushing onto the stack we just destroy the object directly
279:     this->allocator().destroy(std::move(obj));
280:   }
281:   return 0;
282: }

284: } // namespace Petsc

286: #endif /* __cplusplus */

288: #endif /* PETSCOBJECTPOOL_HPP */