1*0e6b6b59SJacob Faibussowitsch #ifndef PETSC_SEGMENTEDMEMPOOL_HPP 2*0e6b6b59SJacob Faibussowitsch #define PETSC_SEGMENTEDMEMPOOL_HPP 3*0e6b6b59SJacob Faibussowitsch 4*0e6b6b59SJacob Faibussowitsch #include <petsc/private/deviceimpl.h> 5*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/macros.hpp> 6*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/type_traits.hpp> 7*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/utility.hpp> 8*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/register_finalize.hpp> 9*0e6b6b59SJacob Faibussowitsch 10*0e6b6b59SJacob Faibussowitsch #include <limits> 11*0e6b6b59SJacob Faibussowitsch #include <deque> 12*0e6b6b59SJacob Faibussowitsch #include <vector> 13*0e6b6b59SJacob Faibussowitsch 14*0e6b6b59SJacob Faibussowitsch namespace Petsc { 15*0e6b6b59SJacob Faibussowitsch 16*0e6b6b59SJacob Faibussowitsch namespace device { 17*0e6b6b59SJacob Faibussowitsch 18*0e6b6b59SJacob Faibussowitsch template <typename T> 19*0e6b6b59SJacob Faibussowitsch class StreamBase { 20*0e6b6b59SJacob Faibussowitsch public: 21*0e6b6b59SJacob Faibussowitsch using id_type = int; 22*0e6b6b59SJacob Faibussowitsch using derived_type = T; 23*0e6b6b59SJacob Faibussowitsch 24*0e6b6b59SJacob Faibussowitsch static const id_type INVALID_ID; 25*0e6b6b59SJacob Faibussowitsch 26*0e6b6b59SJacob Faibussowitsch // needed so that dependent auto works, see veccupmimpl.h for a detailed discussion 27*0e6b6b59SJacob Faibussowitsch template <typename U = T> 28*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD auto get_stream() const noexcept PETSC_DECLTYPE_AUTO_RETURNS(static_cast<const U &>(*this).get_stream_()); 29*0e6b6b59SJacob Faibussowitsch 30*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD id_type get_id() const noexcept { return static_cast<const T &>(*this).get_id_(); } 31*0e6b6b59SJacob Faibussowitsch 32*0e6b6b59SJacob Faibussowitsch template <typename E> 33*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode record_event(E &&event) const noexcept { 34*0e6b6b59SJacob Faibussowitsch return static_cast<const T &>(*this).record_event_(std::forward<E>(event)); 35*0e6b6b59SJacob Faibussowitsch } 36*0e6b6b59SJacob Faibussowitsch 37*0e6b6b59SJacob Faibussowitsch template <typename E> 38*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode wait_for_event(E &&event) const noexcept { 39*0e6b6b59SJacob Faibussowitsch return static_cast<const T &>(*this).wait_for_(std::forward<E>(event)); 40*0e6b6b59SJacob Faibussowitsch } 41*0e6b6b59SJacob Faibussowitsch 42*0e6b6b59SJacob Faibussowitsch protected: 43*0e6b6b59SJacob Faibussowitsch constexpr StreamBase() noexcept = default; 44*0e6b6b59SJacob Faibussowitsch 45*0e6b6b59SJacob Faibussowitsch struct default_event_type { }; 46*0e6b6b59SJacob Faibussowitsch using default_stream_type = std::nullptr_t; 47*0e6b6b59SJacob Faibussowitsch 48*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr default_stream_type get_stream_() noexcept { return nullptr; } 49*0e6b6b59SJacob Faibussowitsch 50*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr id_type get_id_() noexcept { return 0; } 51*0e6b6b59SJacob Faibussowitsch 52*0e6b6b59SJacob Faibussowitsch template <typename U = T> 53*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr PetscErrorCode record_event_(const typename U::event_type &) noexcept { 54*0e6b6b59SJacob Faibussowitsch return 0; 55*0e6b6b59SJacob Faibussowitsch } 56*0e6b6b59SJacob Faibussowitsch 57*0e6b6b59SJacob Faibussowitsch template <typename U = T> 58*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr PetscErrorCode wait_for_(const typename U::event_type &) noexcept { 59*0e6b6b59SJacob Faibussowitsch return 0; 60*0e6b6b59SJacob Faibussowitsch } 61*0e6b6b59SJacob Faibussowitsch }; 62*0e6b6b59SJacob Faibussowitsch 63*0e6b6b59SJacob Faibussowitsch template <typename T> 64*0e6b6b59SJacob Faibussowitsch const typename StreamBase<T>::id_type StreamBase<T>::INVALID_ID = -1; 65*0e6b6b59SJacob Faibussowitsch 66*0e6b6b59SJacob Faibussowitsch struct DefaultStream : StreamBase<DefaultStream> { 67*0e6b6b59SJacob Faibussowitsch using stream_type = typename StreamBase<DefaultStream>::default_stream_type; 68*0e6b6b59SJacob Faibussowitsch using id_type = typename StreamBase<DefaultStream>::id_type; 69*0e6b6b59SJacob Faibussowitsch using event_type = typename StreamBase<DefaultStream>::default_event_type; 70*0e6b6b59SJacob Faibussowitsch }; 71*0e6b6b59SJacob Faibussowitsch 72*0e6b6b59SJacob Faibussowitsch } // namespace device 73*0e6b6b59SJacob Faibussowitsch 74*0e6b6b59SJacob Faibussowitsch namespace memory { 75*0e6b6b59SJacob Faibussowitsch 76*0e6b6b59SJacob Faibussowitsch namespace impl { 77*0e6b6b59SJacob Faibussowitsch 78*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 79*0e6b6b59SJacob Faibussowitsch // MemoryChunk 80*0e6b6b59SJacob Faibussowitsch // 81*0e6b6b59SJacob Faibussowitsch // Represents a checked-out region of a MemoryBlock. Tracks the offset into the owning 82*0e6b6b59SJacob Faibussowitsch // MemoryBlock and its size/capacity 83*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 84*0e6b6b59SJacob Faibussowitsch 85*0e6b6b59SJacob Faibussowitsch template <typename EventType> 86*0e6b6b59SJacob Faibussowitsch class MemoryChunk { 87*0e6b6b59SJacob Faibussowitsch public: 88*0e6b6b59SJacob Faibussowitsch using event_type = EventType; 89*0e6b6b59SJacob Faibussowitsch using size_type = std::size_t; 90*0e6b6b59SJacob Faibussowitsch 91*0e6b6b59SJacob Faibussowitsch MemoryChunk(size_type, size_type) noexcept; 92*0e6b6b59SJacob Faibussowitsch explicit MemoryChunk(size_type) noexcept; 93*0e6b6b59SJacob Faibussowitsch 94*0e6b6b59SJacob Faibussowitsch MemoryChunk(MemoryChunk &&) noexcept; 95*0e6b6b59SJacob Faibussowitsch MemoryChunk &operator=(MemoryChunk &&) noexcept; 96*0e6b6b59SJacob Faibussowitsch 97*0e6b6b59SJacob Faibussowitsch MemoryChunk(const MemoryChunk &) noexcept = delete; 98*0e6b6b59SJacob Faibussowitsch MemoryChunk &operator=(const MemoryChunk &) noexcept = delete; 99*0e6b6b59SJacob Faibussowitsch 100*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type start() const noexcept { return start_; } 101*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type size() const noexcept { return size_; } 102*0e6b6b59SJacob Faibussowitsch // REVIEW ME: 103*0e6b6b59SJacob Faibussowitsch // make this an actual field, normally each chunk shrinks_to_fit() on begin claimed, but in 104*0e6b6b59SJacob Faibussowitsch // theory only the last chunk needs to do this 105*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type capacity() const noexcept { return size_; } 106*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type total_offset() const noexcept { return start() + size(); } 107*0e6b6b59SJacob Faibussowitsch 108*0e6b6b59SJacob Faibussowitsch template <typename U> 109*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode release(const device::StreamBase<U> *) noexcept; 110*0e6b6b59SJacob Faibussowitsch template <typename U> 111*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode claim(const device::StreamBase<U> *, size_type, bool *, bool = false) noexcept; 112*0e6b6b59SJacob Faibussowitsch template <typename U> 113*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool can_claim(const device::StreamBase<U> *, size_type, bool) const noexcept; 114*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode resize(size_type) noexcept; 115*0e6b6b59SJacob Faibussowitsch 116*0e6b6b59SJacob Faibussowitsch private: 117*0e6b6b59SJacob Faibussowitsch // clang-format off 118*0e6b6b59SJacob Faibussowitsch event_type event_{}; // event recorded when the chunk was released 119*0e6b6b59SJacob Faibussowitsch bool open_ = true; // is this chunk open? 120*0e6b6b59SJacob Faibussowitsch // id of the last stream to use the chunk, populated on release 121*0e6b6b59SJacob Faibussowitsch int stream_id_ = device::DefaultStream::INVALID_ID; 122*0e6b6b59SJacob Faibussowitsch size_type size_ = 0; // size of the chunk 123*0e6b6b59SJacob Faibussowitsch const size_type start_ = 0; // offset from the start of the owning block 124*0e6b6b59SJacob Faibussowitsch 125*0e6b6b59SJacob Faibussowitsch // clang-format on 126*0e6b6b59SJacob Faibussowitsch template <typename U> 127*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool stream_compat_(const device::StreamBase<U> *) const noexcept; 128*0e6b6b59SJacob Faibussowitsch }; 129*0e6b6b59SJacob Faibussowitsch 130*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 131*0e6b6b59SJacob Faibussowitsch // MemoryChunk - Private API 132*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 133*0e6b6b59SJacob Faibussowitsch 134*0e6b6b59SJacob Faibussowitsch // asks and answers the question: can this stream claim this chunk without serializing? 135*0e6b6b59SJacob Faibussowitsch template <typename E> 136*0e6b6b59SJacob Faibussowitsch template <typename U> 137*0e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::stream_compat_(const device::StreamBase<U> *strm) const noexcept { 138*0e6b6b59SJacob Faibussowitsch return (stream_id_ == strm->INVALID_ID) || (stream_id_ == strm->get_id()); 139*0e6b6b59SJacob Faibussowitsch } 140*0e6b6b59SJacob Faibussowitsch 141*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 142*0e6b6b59SJacob Faibussowitsch // MemoryChunk - Public API 143*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 144*0e6b6b59SJacob Faibussowitsch 145*0e6b6b59SJacob Faibussowitsch template <typename E> 146*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type start, size_type size) noexcept : size_(size), start_(start) { } 147*0e6b6b59SJacob Faibussowitsch 148*0e6b6b59SJacob Faibussowitsch template <typename E> 149*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type size) noexcept : MemoryChunk(0, size) { } 150*0e6b6b59SJacob Faibussowitsch 151*0e6b6b59SJacob Faibussowitsch template <typename E> 152*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(MemoryChunk<E> &&other) noexcept : 153*0e6b6b59SJacob Faibussowitsch event_(std::move(other.event_)), open_(util::exchange(other.open_, false)), stream_id_(util::exchange(other.stream_id_, device::DefaultStream::INVALID_ID)), size_(util::exchange(other.size_, 0)), start_(std::move(other.start_)) { } 154*0e6b6b59SJacob Faibussowitsch 155*0e6b6b59SJacob Faibussowitsch template <typename E> 156*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E> &MemoryChunk<E>::operator=(MemoryChunk<E> &&other) noexcept { 157*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 158*0e6b6b59SJacob Faibussowitsch if (this != &other) { 159*0e6b6b59SJacob Faibussowitsch event_ = std::move(other.event_); 160*0e6b6b59SJacob Faibussowitsch open_ = util::exchange(other.open_, false); 161*0e6b6b59SJacob Faibussowitsch stream_id_ = util::exchange(other.stream_id_, device::DefaultStream::INVALID_ID); 162*0e6b6b59SJacob Faibussowitsch size_ = util::exchange(other.size_, 0); 163*0e6b6b59SJacob Faibussowitsch start_ = std::move(other.start_); 164*0e6b6b59SJacob Faibussowitsch } 165*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(*this); 166*0e6b6b59SJacob Faibussowitsch } 167*0e6b6b59SJacob Faibussowitsch 168*0e6b6b59SJacob Faibussowitsch /* 169*0e6b6b59SJacob Faibussowitsch MemoryChunk::release - release a chunk on a stream 170*0e6b6b59SJacob Faibussowitsch 171*0e6b6b59SJacob Faibussowitsch Input Parameter: 172*0e6b6b59SJacob Faibussowitsch . stream - the stream to release the chunk with 173*0e6b6b59SJacob Faibussowitsch 174*0e6b6b59SJacob Faibussowitsch Notes: 175*0e6b6b59SJacob Faibussowitsch Inserts a release operation on stream and records the state of stream at the time this 176*0e6b6b59SJacob Faibussowitsch routine was called. 177*0e6b6b59SJacob Faibussowitsch 178*0e6b6b59SJacob Faibussowitsch Future allocation requests which attempt to claim the chunk on the same stream may re-acquire 179*0e6b6b59SJacob Faibussowitsch the chunk without serialization. 180*0e6b6b59SJacob Faibussowitsch 181*0e6b6b59SJacob Faibussowitsch If another stream attempts to claim the chunk they must wait for the recorded event before 182*0e6b6b59SJacob Faibussowitsch claiming the chunk. 183*0e6b6b59SJacob Faibussowitsch */ 184*0e6b6b59SJacob Faibussowitsch template <typename E> 185*0e6b6b59SJacob Faibussowitsch template <typename U> 186*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::release(const device::StreamBase<U> *stream) noexcept { 187*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 188*0e6b6b59SJacob Faibussowitsch open_ = true; 189*0e6b6b59SJacob Faibussowitsch stream_id_ = stream->get_id(); 190*0e6b6b59SJacob Faibussowitsch PetscCall(stream->record_event(event_)); 191*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 192*0e6b6b59SJacob Faibussowitsch } 193*0e6b6b59SJacob Faibussowitsch 194*0e6b6b59SJacob Faibussowitsch /* 195*0e6b6b59SJacob Faibussowitsch MemoryChunk::claim - attempt to claim a particular chunk 196*0e6b6b59SJacob Faibussowitsch 197*0e6b6b59SJacob Faibussowitsch Input Parameters: 198*0e6b6b59SJacob Faibussowitsch + stream - the stream on which to attempt to claim 199*0e6b6b59SJacob Faibussowitsch . req_size - the requested size (in elements) to attempt to claim 200*0e6b6b59SJacob Faibussowitsch - serialize - (optional, false) whether the claimant allows serialization 201*0e6b6b59SJacob Faibussowitsch 202*0e6b6b59SJacob Faibussowitsch Output Parameter: 203*0e6b6b59SJacob Faibussowitsch . success - true if the chunk was claimed, false otherwise 204*0e6b6b59SJacob Faibussowitsch */ 205*0e6b6b59SJacob Faibussowitsch template <typename E> 206*0e6b6b59SJacob Faibussowitsch template <typename U> 207*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::claim(const device::StreamBase<U> *stream, size_type req_size, bool *success, bool serialize) noexcept { 208*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 209*0e6b6b59SJacob Faibussowitsch if ((*success = can_claim(stream, req_size, serialize))) { 210*0e6b6b59SJacob Faibussowitsch if (serialize && !stream_compat_(stream)) PetscCall(stream->wait_for_event(event_)); 211*0e6b6b59SJacob Faibussowitsch PetscCall(resize(req_size)); 212*0e6b6b59SJacob Faibussowitsch open_ = false; 213*0e6b6b59SJacob Faibussowitsch } 214*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 215*0e6b6b59SJacob Faibussowitsch } 216*0e6b6b59SJacob Faibussowitsch 217*0e6b6b59SJacob Faibussowitsch /* 218*0e6b6b59SJacob Faibussowitsch MemoryChunk::can_claim - test whether a particular chunk can be claimed 219*0e6b6b59SJacob Faibussowitsch 220*0e6b6b59SJacob Faibussowitsch Input Parameters: 221*0e6b6b59SJacob Faibussowitsch + stream - the stream on which to attempt to claim 222*0e6b6b59SJacob Faibussowitsch . req_size - the requested size (in elements) to attempt to claim 223*0e6b6b59SJacob Faibussowitsch - serialize - whether the claimant allows serialization 224*0e6b6b59SJacob Faibussowitsch 225*0e6b6b59SJacob Faibussowitsch Output: 226*0e6b6b59SJacob Faibussowitsch . [return] - true if the chunk is claimable given the configuration, false otherwise 227*0e6b6b59SJacob Faibussowitsch */ 228*0e6b6b59SJacob Faibussowitsch template <typename E> 229*0e6b6b59SJacob Faibussowitsch template <typename U> 230*0e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::can_claim(const device::StreamBase<U> *stream, size_type req_size, bool serialize) const noexcept { 231*0e6b6b59SJacob Faibussowitsch if (open_ && (req_size <= capacity())) { 232*0e6b6b59SJacob Faibussowitsch // fully compatible 233*0e6b6b59SJacob Faibussowitsch if (stream_compat_(stream)) return true; 234*0e6b6b59SJacob Faibussowitsch // stream wasn't compatible, but could claim if we serialized 235*0e6b6b59SJacob Faibussowitsch if (serialize) return true; 236*0e6b6b59SJacob Faibussowitsch // incompatible stream and did not want to serialize 237*0e6b6b59SJacob Faibussowitsch } 238*0e6b6b59SJacob Faibussowitsch return false; 239*0e6b6b59SJacob Faibussowitsch } 240*0e6b6b59SJacob Faibussowitsch 241*0e6b6b59SJacob Faibussowitsch /* 242*0e6b6b59SJacob Faibussowitsch MemoryChunk::resize - grow a chunk to new size 243*0e6b6b59SJacob Faibussowitsch 244*0e6b6b59SJacob Faibussowitsch Input Parameter: 245*0e6b6b59SJacob Faibussowitsch . newsize - the new size Requested 246*0e6b6b59SJacob Faibussowitsch 247*0e6b6b59SJacob Faibussowitsch Notes: 248*0e6b6b59SJacob Faibussowitsch newsize cannot be larger than capacity 249*0e6b6b59SJacob Faibussowitsch */ 250*0e6b6b59SJacob Faibussowitsch template <typename E> 251*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::resize(size_type newsize) noexcept { 252*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 253*0e6b6b59SJacob Faibussowitsch PetscAssert(newsize <= capacity(), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "New size %zu larger than capacity %zu", newsize, capacity()); 254*0e6b6b59SJacob Faibussowitsch size_ = newsize; 255*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 256*0e6b6b59SJacob Faibussowitsch } 257*0e6b6b59SJacob Faibussowitsch 258*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 259*0e6b6b59SJacob Faibussowitsch // MemoryBlock 260*0e6b6b59SJacob Faibussowitsch // 261*0e6b6b59SJacob Faibussowitsch // A "memory block" manager, which owns the pointer to a particular memory range. Retrieving 262*0e6b6b59SJacob Faibussowitsch // and restoring a block is thread-safe (so may be used by multiple device streams). 263*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 264*0e6b6b59SJacob Faibussowitsch 265*0e6b6b59SJacob Faibussowitsch template <typename T, typename AllocatorType, typename StreamType> 266*0e6b6b59SJacob Faibussowitsch class MemoryBlock { 267*0e6b6b59SJacob Faibussowitsch public: 268*0e6b6b59SJacob Faibussowitsch using value_type = T; 269*0e6b6b59SJacob Faibussowitsch using allocator_type = AllocatorType; 270*0e6b6b59SJacob Faibussowitsch using stream_type = StreamType; 271*0e6b6b59SJacob Faibussowitsch using event_type = typename stream_type::event_type; 272*0e6b6b59SJacob Faibussowitsch using chunk_type = MemoryChunk<event_type>; 273*0e6b6b59SJacob Faibussowitsch using size_type = typename chunk_type::size_type; 274*0e6b6b59SJacob Faibussowitsch using chunk_list_type = std::vector<chunk_type>; 275*0e6b6b59SJacob Faibussowitsch 276*0e6b6b59SJacob Faibussowitsch template <typename U> 277*0e6b6b59SJacob Faibussowitsch MemoryBlock(allocator_type *, size_type, const device::StreamBase<U> *) noexcept; 278*0e6b6b59SJacob Faibussowitsch 279*0e6b6b59SJacob Faibussowitsch ~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value); 280*0e6b6b59SJacob Faibussowitsch 281*0e6b6b59SJacob Faibussowitsch MemoryBlock(MemoryBlock &&) noexcept; 282*0e6b6b59SJacob Faibussowitsch MemoryBlock &operator=(MemoryBlock &&) noexcept; 283*0e6b6b59SJacob Faibussowitsch 284*0e6b6b59SJacob Faibussowitsch // memory blocks are not copyable 285*0e6b6b59SJacob Faibussowitsch MemoryBlock(const MemoryBlock &) = delete; 286*0e6b6b59SJacob Faibussowitsch MemoryBlock &operator=(const MemoryBlock &) = delete; 287*0e6b6b59SJacob Faibussowitsch 288*0e6b6b59SJacob Faibussowitsch /* --- actual functions --- */ 289*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_allocate_chunk(size_type, T **, const stream_type *, bool *) noexcept; 290*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_deallocate_chunk(T **, const stream_type *, bool *) noexcept; 291*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_find_chunk(const T *, chunk_type **) noexcept; 292*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool owns_pointer(const T *) const noexcept; 293*0e6b6b59SJacob Faibussowitsch 294*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type size() const noexcept { return size_; } 295*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type bytes() const noexcept { return sizeof(value_type) * size(); } 296*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type num_chunks() const noexcept { return chunks_.size(); } 297*0e6b6b59SJacob Faibussowitsch 298*0e6b6b59SJacob Faibussowitsch private: 299*0e6b6b59SJacob Faibussowitsch value_type *mem_{}; 300*0e6b6b59SJacob Faibussowitsch allocator_type *allocator_{}; 301*0e6b6b59SJacob Faibussowitsch size_type size_{}; 302*0e6b6b59SJacob Faibussowitsch chunk_list_type chunks_{}; 303*0e6b6b59SJacob Faibussowitsch 304*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode clear_(const stream_type *) noexcept; 305*0e6b6b59SJacob Faibussowitsch }; 306*0e6b6b59SJacob Faibussowitsch 307*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 308*0e6b6b59SJacob Faibussowitsch // MemoryBlock - Private API 309*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 310*0e6b6b59SJacob Faibussowitsch 311*0e6b6b59SJacob Faibussowitsch // clear the memory block, called from destructors and move assignment/construction 312*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 313*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode MemoryBlock<T, A, S>::clear_(const stream_type *stream) noexcept { 314*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 315*0e6b6b59SJacob Faibussowitsch if (PetscLikely(mem_)) { 316*0e6b6b59SJacob Faibussowitsch PetscCall(allocator_->deallocate(mem_, stream)); 317*0e6b6b59SJacob Faibussowitsch mem_ = nullptr; 318*0e6b6b59SJacob Faibussowitsch } 319*0e6b6b59SJacob Faibussowitsch size_ = 0; 320*0e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.clear()); 321*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 322*0e6b6b59SJacob Faibussowitsch } 323*0e6b6b59SJacob Faibussowitsch 324*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 325*0e6b6b59SJacob Faibussowitsch // MemoryBlock - Public API 326*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 327*0e6b6b59SJacob Faibussowitsch 328*0e6b6b59SJacob Faibussowitsch // default constructor, allocates memory immediately 329*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 330*0e6b6b59SJacob Faibussowitsch template <typename U> 331*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::MemoryBlock(allocator_type *alloc, size_type s, const device::StreamBase<U> *stream) noexcept : size_(s), allocator_(alloc) { 332*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 333*0e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, alloc->allocate(&mem_, s, stream)); 334*0e6b6b59SJacob Faibussowitsch PetscAssertAbort(mem_, PETSC_COMM_SELF, PETSC_ERR_MEM, "Failed to allocate memory block of size %zu", s); 335*0e6b6b59SJacob Faibussowitsch PetscFunctionReturnVoid(); 336*0e6b6b59SJacob Faibussowitsch } 337*0e6b6b59SJacob Faibussowitsch 338*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 339*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value) { 340*0e6b6b59SJacob Faibussowitsch stream_type stream; 341*0e6b6b59SJacob Faibussowitsch 342*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 343*0e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, clear_(&stream)); 344*0e6b6b59SJacob Faibussowitsch PetscFunctionReturnVoid(); 345*0e6b6b59SJacob Faibussowitsch } 346*0e6b6b59SJacob Faibussowitsch 347*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 348*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::MemoryBlock(MemoryBlock &&other) noexcept : mem_(util::exchange(other.mem_, nullptr)), allocator_(other.allocator_), size_(util::exchange(other.size_, 0)), chunks_(std::move(other.chunks_)) { } 349*0e6b6b59SJacob Faibussowitsch 350*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 351*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S> &MemoryBlock<T, A, S>::operator=(MemoryBlock &&other) noexcept { 352*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 353*0e6b6b59SJacob Faibussowitsch if (this != &other) { 354*0e6b6b59SJacob Faibussowitsch stream_type stream; 355*0e6b6b59SJacob Faibussowitsch 356*0e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, clear_(&stream)); 357*0e6b6b59SJacob Faibussowitsch mem_ = util::exchange(other.mem_, nullptr); 358*0e6b6b59SJacob Faibussowitsch allocator_ = other.allocator_; 359*0e6b6b59SJacob Faibussowitsch size_ = util::exchange(other.size_, 0); 360*0e6b6b59SJacob Faibussowitsch chunks_ = std::move(other.chunks_); 361*0e6b6b59SJacob Faibussowitsch } 362*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(*this); 363*0e6b6b59SJacob Faibussowitsch } 364*0e6b6b59SJacob Faibussowitsch 365*0e6b6b59SJacob Faibussowitsch /* 366*0e6b6b59SJacob Faibussowitsch MemoryBock::owns_pointer - returns true if this block owns a pointer, false otherwise 367*0e6b6b59SJacob Faibussowitsch */ 368*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 369*0e6b6b59SJacob Faibussowitsch inline bool MemoryBlock<T, A, S>::owns_pointer(const T *ptr) const noexcept { 370*0e6b6b59SJacob Faibussowitsch // each pool is linear in memory, so it suffices to check the bounds 371*0e6b6b59SJacob Faibussowitsch return (ptr >= mem_) && (ptr < std::next(mem_, size())); 372*0e6b6b59SJacob Faibussowitsch } 373*0e6b6b59SJacob Faibussowitsch 374*0e6b6b59SJacob Faibussowitsch /* 375*0e6b6b59SJacob Faibussowitsch MemoryBlock::try_allocate_chunk - try to get a chunk from this MemoryBlock 376*0e6b6b59SJacob Faibussowitsch 377*0e6b6b59SJacob Faibussowitsch Input Parameters: 378*0e6b6b59SJacob Faibussowitsch + req_size - the requested size of the allocation (in elements) 379*0e6b6b59SJacob Faibussowitsch . ptr - ptr to fill 380*0e6b6b59SJacob Faibussowitsch - stream - stream to fill the pointer on 381*0e6b6b59SJacob Faibussowitsch 382*0e6b6b59SJacob Faibussowitsch Output Parameter: 383*0e6b6b59SJacob Faibussowitsch . success - true if chunk was gotten, false otherwise 384*0e6b6b59SJacob Faibussowitsch 385*0e6b6b59SJacob Faibussowitsch Notes: 386*0e6b6b59SJacob Faibussowitsch If the current memory could not satisfy the memory request, ptr is unchanged 387*0e6b6b59SJacob Faibussowitsch */ 388*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 389*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_allocate_chunk(size_type req_size, T **ptr, const stream_type *stream, bool *success) noexcept { 390*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 391*0e6b6b59SJacob Faibussowitsch *success = false; 392*0e6b6b59SJacob Faibussowitsch if (req_size <= size()) { 393*0e6b6b59SJacob Faibussowitsch const auto try_create_chunk = [&]() { 394*0e6b6b59SJacob Faibussowitsch const auto was_empty = chunks_.empty(); 395*0e6b6b59SJacob Faibussowitsch const auto block_alloced = was_empty ? 0 : chunks_.back().total_offset(); 396*0e6b6b59SJacob Faibussowitsch 397*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 398*0e6b6b59SJacob Faibussowitsch if (block_alloced + req_size <= size()) { 399*0e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.emplace_back(block_alloced, req_size)); 400*0e6b6b59SJacob Faibussowitsch PetscCall(chunks_.back().claim(stream, req_size, success)); 401*0e6b6b59SJacob Faibussowitsch *ptr = mem_ + block_alloced; 402*0e6b6b59SJacob Faibussowitsch if (was_empty) PetscAssert(*success, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Failed to claim chunk (of size %zu) even though block (of size %zu) was empty!", req_size, size()); 403*0e6b6b59SJacob Faibussowitsch } 404*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 405*0e6b6b59SJacob Faibussowitsch }; 406*0e6b6b59SJacob Faibussowitsch const auto try_find_open_chunk = [&](bool serialize = false) { 407*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 408*0e6b6b59SJacob Faibussowitsch for (auto &chunk : chunks_) { 409*0e6b6b59SJacob Faibussowitsch PetscCall(chunk.claim(stream, req_size, success, serialize)); 410*0e6b6b59SJacob Faibussowitsch if (*success) { 411*0e6b6b59SJacob Faibussowitsch *ptr = mem_ + chunk.start(); 412*0e6b6b59SJacob Faibussowitsch break; 413*0e6b6b59SJacob Faibussowitsch } 414*0e6b6b59SJacob Faibussowitsch } 415*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 416*0e6b6b59SJacob Faibussowitsch }; 417*0e6b6b59SJacob Faibussowitsch 418*0e6b6b59SJacob Faibussowitsch // search previously distributed chunks, but only claim one if it is on the same stream 419*0e6b6b59SJacob Faibussowitsch // as us 420*0e6b6b59SJacob Faibussowitsch PetscCall(try_find_open_chunk()); 421*0e6b6b59SJacob Faibussowitsch 422*0e6b6b59SJacob Faibussowitsch // if we are here we couldn't reuse one of our own chunks so check first if the pool 423*0e6b6b59SJacob Faibussowitsch // has room for a new one 424*0e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_create_chunk()); 425*0e6b6b59SJacob Faibussowitsch 426*0e6b6b59SJacob Faibussowitsch // try pruning dead chunks off the back, note we do this regardless of whether we are 427*0e6b6b59SJacob Faibussowitsch // successful 428*0e6b6b59SJacob Faibussowitsch while (chunks_.back().can_claim(stream, 0, false)) { 429*0e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.pop_back()); 430*0e6b6b59SJacob Faibussowitsch if (chunks_.empty()) { 431*0e6b6b59SJacob Faibussowitsch // if chunks are empty it implies we have managed to claim (and subsequently destroy) 432*0e6b6b59SJacob Faibussowitsch // our own chunk twice! something has gone wrong 433*0e6b6b59SJacob Faibussowitsch PetscAssert(!*success, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Successfully claimed a chunk (of size %zu, from block of size %zu) but have now managed to claim it for a second time (and destroyed it)!", req_size, size()); 434*0e6b6b59SJacob Faibussowitsch break; 435*0e6b6b59SJacob Faibussowitsch } 436*0e6b6b59SJacob Faibussowitsch } 437*0e6b6b59SJacob Faibussowitsch 438*0e6b6b59SJacob Faibussowitsch // if previously unsuccessful see if enough space has opened up due to pruning. note that 439*0e6b6b59SJacob Faibussowitsch // if the chunk list was emptied from the pruning this call must succeed in allocating a 440*0e6b6b59SJacob Faibussowitsch // chunk, otherwise something is wrong 441*0e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_create_chunk()); 442*0e6b6b59SJacob Faibussowitsch 443*0e6b6b59SJacob Faibussowitsch // last resort, iterate over all chunks and see if we can steal one by waiting on the 444*0e6b6b59SJacob Faibussowitsch // current owner to finish using it 445*0e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_find_open_chunk(true)); 446*0e6b6b59SJacob Faibussowitsch 447*0e6b6b59SJacob Faibussowitsch // sets memory to NaN or infinity depending on the type to catch out uninitialized memory 448*0e6b6b59SJacob Faibussowitsch // accesses. 449*0e6b6b59SJacob Faibussowitsch if (PetscDefined(USE_DEBUG) && *success) PetscCall(allocator_->set_canary(*ptr, req_size, stream)); 450*0e6b6b59SJacob Faibussowitsch } 451*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 452*0e6b6b59SJacob Faibussowitsch } 453*0e6b6b59SJacob Faibussowitsch 454*0e6b6b59SJacob Faibussowitsch /* 455*0e6b6b59SJacob Faibussowitsch MemoryBlock::try_deallocate_chunk - try to restore a chunk to this MemoryBlock 456*0e6b6b59SJacob Faibussowitsch 457*0e6b6b59SJacob Faibussowitsch Input Parameters: 458*0e6b6b59SJacob Faibussowitsch + ptr - ptr to restore 459*0e6b6b59SJacob Faibussowitsch - stream - stream to restore the pointer on 460*0e6b6b59SJacob Faibussowitsch 461*0e6b6b59SJacob Faibussowitsch Output Parameter: 462*0e6b6b59SJacob Faibussowitsch . success - true if chunk was restored, false otherwise 463*0e6b6b59SJacob Faibussowitsch 464*0e6b6b59SJacob Faibussowitsch Notes: 465*0e6b6b59SJacob Faibussowitsch ptr is set to nullptr on successful restore, and is unchanged otherwise. If the ptr is owned 466*0e6b6b59SJacob Faibussowitsch by this MemoryBlock then it is restored on stream. The same stream may recieve ptr again 467*0e6b6b59SJacob Faibussowitsch without synchronization, but other streams may not do so until either serializing or the 468*0e6b6b59SJacob Faibussowitsch stream is idle again. 469*0e6b6b59SJacob Faibussowitsch */ 470*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 471*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_deallocate_chunk(T **ptr, const stream_type *stream, bool *success) noexcept { 472*0e6b6b59SJacob Faibussowitsch chunk_type *chunk = nullptr; 473*0e6b6b59SJacob Faibussowitsch 474*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 475*0e6b6b59SJacob Faibussowitsch PetscCall(try_find_chunk(*ptr, &chunk)); 476*0e6b6b59SJacob Faibussowitsch if (chunk) { 477*0e6b6b59SJacob Faibussowitsch PetscCall(chunk->release(stream)); 478*0e6b6b59SJacob Faibussowitsch *ptr = nullptr; 479*0e6b6b59SJacob Faibussowitsch *success = true; 480*0e6b6b59SJacob Faibussowitsch } else { 481*0e6b6b59SJacob Faibussowitsch *success = false; 482*0e6b6b59SJacob Faibussowitsch } 483*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 484*0e6b6b59SJacob Faibussowitsch } 485*0e6b6b59SJacob Faibussowitsch 486*0e6b6b59SJacob Faibussowitsch /* 487*0e6b6b59SJacob Faibussowitsch MemoryBlock::try_find_chunk - try to find the chunk which owns ptr 488*0e6b6b59SJacob Faibussowitsch 489*0e6b6b59SJacob Faibussowitsch Input Parameter: 490*0e6b6b59SJacob Faibussowitsch . ptr - the pointer to lookk for 491*0e6b6b59SJacob Faibussowitsch 492*0e6b6b59SJacob Faibussowitsch Output Parameter: 493*0e6b6b59SJacob Faibussowitsch . ret_chunk - pointer to the owning chunk or nullptr if not found 494*0e6b6b59SJacob Faibussowitsch */ 495*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 496*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_find_chunk(const T *ptr, chunk_type **ret_chunk) noexcept { 497*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 498*0e6b6b59SJacob Faibussowitsch *ret_chunk = nullptr; 499*0e6b6b59SJacob Faibussowitsch if (owns_pointer(ptr)) { 500*0e6b6b59SJacob Faibussowitsch const auto offset = static_cast<size_type>(ptr - mem_); 501*0e6b6b59SJacob Faibussowitsch 502*0e6b6b59SJacob Faibussowitsch for (auto &chunk : chunks_) { 503*0e6b6b59SJacob Faibussowitsch if (chunk.start() == offset) { 504*0e6b6b59SJacob Faibussowitsch *ret_chunk = &chunk; 505*0e6b6b59SJacob Faibussowitsch break; 506*0e6b6b59SJacob Faibussowitsch } 507*0e6b6b59SJacob Faibussowitsch } 508*0e6b6b59SJacob Faibussowitsch 509*0e6b6b59SJacob Faibussowitsch PetscAssert(*ret_chunk, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Failed to find %zu in block, even though it is within block range [%zu, %zu)", reinterpret_cast<uintptr_t>(ptr), reinterpret_cast<uintptr_t>(mem_), reinterpret_cast<uintptr_t>(std::next(mem_, size()))); 510*0e6b6b59SJacob Faibussowitsch } 511*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 512*0e6b6b59SJacob Faibussowitsch } 513*0e6b6b59SJacob Faibussowitsch 514*0e6b6b59SJacob Faibussowitsch namespace detail { 515*0e6b6b59SJacob Faibussowitsch 516*0e6b6b59SJacob Faibussowitsch template <typename T> 517*0e6b6b59SJacob Faibussowitsch struct real_type { 518*0e6b6b59SJacob Faibussowitsch using type = T; 519*0e6b6b59SJacob Faibussowitsch }; 520*0e6b6b59SJacob Faibussowitsch 521*0e6b6b59SJacob Faibussowitsch template <> 522*0e6b6b59SJacob Faibussowitsch struct real_type<PetscScalar> { 523*0e6b6b59SJacob Faibussowitsch using type = PetscReal; 524*0e6b6b59SJacob Faibussowitsch }; 525*0e6b6b59SJacob Faibussowitsch 526*0e6b6b59SJacob Faibussowitsch } // namespace detail 527*0e6b6b59SJacob Faibussowitsch 528*0e6b6b59SJacob Faibussowitsch template <typename T> 529*0e6b6b59SJacob Faibussowitsch struct SegmentedMemoryPoolAllocatorBase { 530*0e6b6b59SJacob Faibussowitsch using value_type = T; 531*0e6b6b59SJacob Faibussowitsch using size_type = std::size_t; 532*0e6b6b59SJacob Faibussowitsch using real_value_type = typename detail::real_type<T>::type; 533*0e6b6b59SJacob Faibussowitsch 534*0e6b6b59SJacob Faibussowitsch template <typename U> 535*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode allocate(value_type **, size_type, const device::StreamBase<U> *) noexcept; 536*0e6b6b59SJacob Faibussowitsch template <typename U> 537*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode deallocate(value_type *, const device::StreamBase<U> *) noexcept; 538*0e6b6b59SJacob Faibussowitsch template <typename U> 539*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode zero(value_type *, size_type, const device::StreamBase<U> *) noexcept; 540*0e6b6b59SJacob Faibussowitsch template <typename U> 541*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode uninitialized_copy(value_type *, const value_type *, size_type, const device::StreamBase<U> *) noexcept; 542*0e6b6b59SJacob Faibussowitsch template <typename U> 543*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode set_canary(value_type *, size_type, const device::StreamBase<U> *) noexcept; 544*0e6b6b59SJacob Faibussowitsch }; 545*0e6b6b59SJacob Faibussowitsch 546*0e6b6b59SJacob Faibussowitsch template <typename T> 547*0e6b6b59SJacob Faibussowitsch template <typename U> 548*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::allocate(value_type **ptr, size_type n, const device::StreamBase<U> *) noexcept { 549*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 550*0e6b6b59SJacob Faibussowitsch PetscCall(PetscMalloc1(n, ptr)); 551*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 552*0e6b6b59SJacob Faibussowitsch } 553*0e6b6b59SJacob Faibussowitsch 554*0e6b6b59SJacob Faibussowitsch template <typename T> 555*0e6b6b59SJacob Faibussowitsch template <typename U> 556*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::deallocate(value_type *ptr, const device::StreamBase<U> *) noexcept { 557*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 558*0e6b6b59SJacob Faibussowitsch PetscCall(PetscFree(ptr)); 559*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 560*0e6b6b59SJacob Faibussowitsch } 561*0e6b6b59SJacob Faibussowitsch 562*0e6b6b59SJacob Faibussowitsch template <typename T> 563*0e6b6b59SJacob Faibussowitsch template <typename U> 564*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::zero(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept { 565*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 566*0e6b6b59SJacob Faibussowitsch PetscCall(PetscArrayzero(ptr, n)); 567*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 568*0e6b6b59SJacob Faibussowitsch } 569*0e6b6b59SJacob Faibussowitsch 570*0e6b6b59SJacob Faibussowitsch template <typename T> 571*0e6b6b59SJacob Faibussowitsch template <typename U> 572*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::uninitialized_copy(value_type *dest, const value_type *src, size_type n, const device::StreamBase<U> *) noexcept { 573*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 574*0e6b6b59SJacob Faibussowitsch PetscCall(PetscArraycpy(dest, src, n)); 575*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 576*0e6b6b59SJacob Faibussowitsch } 577*0e6b6b59SJacob Faibussowitsch 578*0e6b6b59SJacob Faibussowitsch template <typename T> 579*0e6b6b59SJacob Faibussowitsch template <typename U> 580*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::set_canary(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept { 581*0e6b6b59SJacob Faibussowitsch using limit_type = std::numeric_limits<real_value_type>; 582*0e6b6b59SJacob Faibussowitsch constexpr value_type canary = limit_type::has_signaling_NaN ? limit_type::signaling_NaN() : limit_type::max(); 583*0e6b6b59SJacob Faibussowitsch 584*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 585*0e6b6b59SJacob Faibussowitsch for (size_type i = 0; i < n; ++i) ptr[i] = canary; 586*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 587*0e6b6b59SJacob Faibussowitsch } 588*0e6b6b59SJacob Faibussowitsch 589*0e6b6b59SJacob Faibussowitsch } // namespace impl 590*0e6b6b59SJacob Faibussowitsch 591*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 592*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool 593*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 594*0e6b6b59SJacob Faibussowitsch 595*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType = device::DefaultStream, typename AllocType = impl::SegmentedMemoryPoolAllocatorBase<MemType>, std::size_t DefaultChunkSize = 256> 596*0e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool; 597*0e6b6b59SJacob Faibussowitsch 598*0e6b6b59SJacob Faibussowitsch // The actual memory pool class. It is in essence just a wrapper for a list of MemoryBlocks. 599*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 600*0e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool : public RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>> { 601*0e6b6b59SJacob Faibussowitsch public: 602*0e6b6b59SJacob Faibussowitsch using value_type = MemType; 603*0e6b6b59SJacob Faibussowitsch using stream_type = StreamType; 604*0e6b6b59SJacob Faibussowitsch using allocator_type = AllocType; 605*0e6b6b59SJacob Faibussowitsch using block_type = impl::MemoryBlock<value_type, allocator_type, stream_type>; 606*0e6b6b59SJacob Faibussowitsch using pool_type = std::deque<block_type>; 607*0e6b6b59SJacob Faibussowitsch using size_type = typename block_type::size_type; 608*0e6b6b59SJacob Faibussowitsch 609*0e6b6b59SJacob Faibussowitsch explicit SegmentedMemoryPool(AllocType = AllocType{}, std::size_t = DefaultChunkSize) noexcept(std::is_nothrow_default_constructible<pool_type>::value); 610*0e6b6b59SJacob Faibussowitsch 611*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode allocate(PetscInt, value_type **, const stream_type *) noexcept; 612*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode deallocate(value_type **, const stream_type *) noexcept; 613*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode reallocate(PetscInt, value_type **, const stream_type *) noexcept; 614*0e6b6b59SJacob Faibussowitsch 615*0e6b6b59SJacob Faibussowitsch private: 616*0e6b6b59SJacob Faibussowitsch pool_type pool_; 617*0e6b6b59SJacob Faibussowitsch allocator_type allocator_; 618*0e6b6b59SJacob Faibussowitsch size_type chunk_size_; 619*0e6b6b59SJacob Faibussowitsch 620*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode make_block_(size_type, const stream_type *) noexcept; 621*0e6b6b59SJacob Faibussowitsch 622*0e6b6b59SJacob Faibussowitsch friend class RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>>; 623*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode register_finalize_(const stream_type *) noexcept; 624*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode finalize_() noexcept; 625*0e6b6b59SJacob Faibussowitsch }; 626*0e6b6b59SJacob Faibussowitsch 627*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 628*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Private API 629*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 630*0e6b6b59SJacob Faibussowitsch 631*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 632*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::make_block_(size_type size, const stream_type *stream) noexcept { 633*0e6b6b59SJacob Faibussowitsch const auto block_size = std::max(size, chunk_size_); 634*0e6b6b59SJacob Faibussowitsch 635*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 636*0e6b6b59SJacob Faibussowitsch PetscCallCXX(pool_.emplace_back(&allocator_, block_size, stream)); 637*0e6b6b59SJacob Faibussowitsch PetscCall(PetscInfo(nullptr, "Allocated new block of size %zu, total %zu blocks\n", block_size, pool_.size())); 638*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 639*0e6b6b59SJacob Faibussowitsch } 640*0e6b6b59SJacob Faibussowitsch 641*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 642*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::register_finalize_(const stream_type *stream) noexcept { 643*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 644*0e6b6b59SJacob Faibussowitsch PetscCall(make_block_(chunk_size_, stream)); 645*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 646*0e6b6b59SJacob Faibussowitsch } 647*0e6b6b59SJacob Faibussowitsch 648*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 649*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::finalize_() noexcept { 650*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 651*0e6b6b59SJacob Faibussowitsch PetscCallCXX(pool_.clear()); 652*0e6b6b59SJacob Faibussowitsch chunk_size_ = DefaultChunkSize; 653*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 654*0e6b6b59SJacob Faibussowitsch } 655*0e6b6b59SJacob Faibussowitsch 656*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 657*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Public API 658*0e6b6b59SJacob Faibussowitsch // ========================================================================================== 659*0e6b6b59SJacob Faibussowitsch 660*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 661*0e6b6b59SJacob Faibussowitsch inline SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::SegmentedMemoryPool(AllocType alloc, std::size_t size) noexcept(std::is_nothrow_default_constructible<pool_type>::value) : 662*0e6b6b59SJacob Faibussowitsch allocator_(std::move(alloc)), chunk_size_(size) { } 663*0e6b6b59SJacob Faibussowitsch 664*0e6b6b59SJacob Faibussowitsch /* 665*0e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::allocate - get an allocation from the memory pool 666*0e6b6b59SJacob Faibussowitsch 667*0e6b6b59SJacob Faibussowitsch Input Parameters: 668*0e6b6b59SJacob Faibussowitsch + req_size - size (in elements) to get 669*0e6b6b59SJacob Faibussowitsch . ptr - the pointer to hold the allocation 670*0e6b6b59SJacob Faibussowitsch - stream - the stream on which to get the allocation 671*0e6b6b59SJacob Faibussowitsch 672*0e6b6b59SJacob Faibussowitsch Output Parameter: 673*0e6b6b59SJacob Faibussowitsch . ptr - the pointer holding the allocation 674*0e6b6b59SJacob Faibussowitsch 675*0e6b6b59SJacob Faibussowitsch Notes: 676*0e6b6b59SJacob Faibussowitsch req_size cannot be negative. If req_size if zero, ptr is set to nullptr 677*0e6b6b59SJacob Faibussowitsch */ 678*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 679*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::allocate(PetscInt req_size, value_type **ptr, const stream_type *stream) noexcept { 680*0e6b6b59SJacob Faibussowitsch const auto size = static_cast<size_type>(req_size); 681*0e6b6b59SJacob Faibussowitsch auto found = false; 682*0e6b6b59SJacob Faibussowitsch 683*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 684*0e6b6b59SJacob Faibussowitsch PetscAssert(req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", req_size); 685*0e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 2); 686*0e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 3); 687*0e6b6b59SJacob Faibussowitsch *ptr = nullptr; 688*0e6b6b59SJacob Faibussowitsch if (!req_size) PetscFunctionReturn(0); 689*0e6b6b59SJacob Faibussowitsch PetscCall(this->register_finalize(PETSC_COMM_SELF, stream)); 690*0e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 691*0e6b6b59SJacob Faibussowitsch PetscCall(block.try_allocate_chunk(size, ptr, stream, &found)); 692*0e6b6b59SJacob Faibussowitsch if (PetscLikely(found)) PetscFunctionReturn(0); 693*0e6b6b59SJacob Faibussowitsch } 694*0e6b6b59SJacob Faibussowitsch 695*0e6b6b59SJacob Faibussowitsch PetscCall(PetscInfo(nullptr, "Could not find an open block in the pool (%zu blocks) (requested size %zu), allocating new block\n", pool_.size(), size)); 696*0e6b6b59SJacob Faibussowitsch // if we are here we couldn't find an open block in the pool, so make a new block 697*0e6b6b59SJacob Faibussowitsch PetscCall(make_block_(size, stream)); 698*0e6b6b59SJacob Faibussowitsch // and assign it 699*0e6b6b59SJacob Faibussowitsch PetscCall(pool_.back().try_allocate_chunk(size, ptr, stream, &found)); 700*0e6b6b59SJacob Faibussowitsch PetscAssert(found, PETSC_COMM_SELF, PETSC_ERR_MEM, "Failed to get a suitable memory chunk (of size %zu) from newly allocated memory block (size %zu)", size, pool_.back().size()); 701*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 702*0e6b6b59SJacob Faibussowitsch } 703*0e6b6b59SJacob Faibussowitsch 704*0e6b6b59SJacob Faibussowitsch /* 705*0e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::deallocate - release a pointer back to the memory pool 706*0e6b6b59SJacob Faibussowitsch 707*0e6b6b59SJacob Faibussowitsch Input Parameters: 708*0e6b6b59SJacob Faibussowitsch + ptr - the pointer to release 709*0e6b6b59SJacob Faibussowitsch - stream - the stream to release it on 710*0e6b6b59SJacob Faibussowitsch 711*0e6b6b59SJacob Faibussowitsch Notes: 712*0e6b6b59SJacob Faibussowitsch If ptr is not owned by the pool it is unchanged. 713*0e6b6b59SJacob Faibussowitsch */ 714*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 715*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::deallocate(value_type **ptr, const stream_type *stream) noexcept { 716*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 717*0e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 1); 718*0e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 2); 719*0e6b6b59SJacob Faibussowitsch // nobody owns a nullptr, and if they do then they have bigger problems 720*0e6b6b59SJacob Faibussowitsch if (!*ptr) PetscFunctionReturn(0); 721*0e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 722*0e6b6b59SJacob Faibussowitsch auto found = false; 723*0e6b6b59SJacob Faibussowitsch 724*0e6b6b59SJacob Faibussowitsch PetscCall(block.try_deallocate_chunk(ptr, stream, &found)); 725*0e6b6b59SJacob Faibussowitsch if (PetscLikely(found)) break; 726*0e6b6b59SJacob Faibussowitsch } 727*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 728*0e6b6b59SJacob Faibussowitsch } 729*0e6b6b59SJacob Faibussowitsch 730*0e6b6b59SJacob Faibussowitsch /* 731*0e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::reallocate - Resize an allocated buffer 732*0e6b6b59SJacob Faibussowitsch 733*0e6b6b59SJacob Faibussowitsch Input Parameters: 734*0e6b6b59SJacob Faibussowitsch + new_req_size - the new buffer size 735*0e6b6b59SJacob Faibussowitsch . ptr - pointer to the buffer 736*0e6b6b59SJacob Faibussowitsch - stream - stream to resize with 737*0e6b6b59SJacob Faibussowitsch 738*0e6b6b59SJacob Faibussowitsch Ouput Parameter: 739*0e6b6b59SJacob Faibussowitsch . ptr - pointer to the new region 740*0e6b6b59SJacob Faibussowitsch 741*0e6b6b59SJacob Faibussowitsch Notes: 742*0e6b6b59SJacob Faibussowitsch ptr must have been allocated by the pool. 743*0e6b6b59SJacob Faibussowitsch 744*0e6b6b59SJacob Faibussowitsch It's OK to shrink the buffer, even down to 0 (in which case it is just deallocated). 745*0e6b6b59SJacob Faibussowitsch */ 746*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 747*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::reallocate(PetscInt new_req_size, value_type **ptr, const stream_type *stream) noexcept { 748*0e6b6b59SJacob Faibussowitsch using chunk_type = typename block_type::chunk_type; 749*0e6b6b59SJacob Faibussowitsch 750*0e6b6b59SJacob Faibussowitsch const auto new_size = static_cast<size_type>(new_req_size); 751*0e6b6b59SJacob Faibussowitsch const auto old_ptr = *ptr; 752*0e6b6b59SJacob Faibussowitsch chunk_type *chunk = nullptr; 753*0e6b6b59SJacob Faibussowitsch 754*0e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 755*0e6b6b59SJacob Faibussowitsch PetscAssert(new_req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", new_req_size); 756*0e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 2); 757*0e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 3); 758*0e6b6b59SJacob Faibussowitsch 759*0e6b6b59SJacob Faibussowitsch // if reallocating to zero, just free 760*0e6b6b59SJacob Faibussowitsch if (PetscUnlikely(new_size == 0)) { 761*0e6b6b59SJacob Faibussowitsch PetscCall(deallocate(ptr, stream)); 762*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 763*0e6b6b59SJacob Faibussowitsch } 764*0e6b6b59SJacob Faibussowitsch 765*0e6b6b59SJacob Faibussowitsch // search the blocks for the owning chunk 766*0e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 767*0e6b6b59SJacob Faibussowitsch PetscCall(block.try_find_chunk(old_ptr, &chunk)); 768*0e6b6b59SJacob Faibussowitsch if (chunk) break; // found 769*0e6b6b59SJacob Faibussowitsch } 770*0e6b6b59SJacob Faibussowitsch PetscAssert(chunk, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Memory pool does not own %p, so cannot reallocate it", *ptr); 771*0e6b6b59SJacob Faibussowitsch 772*0e6b6b59SJacob Faibussowitsch if (chunk->capacity() < new_size) { 773*0e6b6b59SJacob Faibussowitsch // chunk does not have enough room, need to grab a fresh chunk and copy to it 774*0e6b6b59SJacob Faibussowitsch *ptr = nullptr; 775*0e6b6b59SJacob Faibussowitsch PetscCall(chunk->release(stream)); 776*0e6b6b59SJacob Faibussowitsch PetscCall(allocate(new_size, ptr, stream)); 777*0e6b6b59SJacob Faibussowitsch PetscCall(allocator_.uninitialized_copy(*ptr, old_ptr, new_size, stream)); 778*0e6b6b59SJacob Faibussowitsch } else { 779*0e6b6b59SJacob Faibussowitsch // chunk had enough room we can simply grow (or shrink) to fit the new size 780*0e6b6b59SJacob Faibussowitsch PetscCall(chunk->resize(new_size)); 781*0e6b6b59SJacob Faibussowitsch } 782*0e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 783*0e6b6b59SJacob Faibussowitsch } 784*0e6b6b59SJacob Faibussowitsch 785*0e6b6b59SJacob Faibussowitsch } // namespace memory 786*0e6b6b59SJacob Faibussowitsch 787*0e6b6b59SJacob Faibussowitsch } // namespace Petsc 788*0e6b6b59SJacob Faibussowitsch 789*0e6b6b59SJacob Faibussowitsch #endif // PETSC_SEGMENTEDMEMPOOL_HPP 790