10e6b6b59SJacob Faibussowitsch #ifndef PETSC_SEGMENTEDMEMPOOL_HPP 20e6b6b59SJacob Faibussowitsch #define PETSC_SEGMENTEDMEMPOOL_HPP 30e6b6b59SJacob Faibussowitsch 40e6b6b59SJacob Faibussowitsch #include <petsc/private/deviceimpl.h> 50e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/macros.hpp> 60e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/type_traits.hpp> 70e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/utility.hpp> 80e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/register_finalize.hpp> 90e6b6b59SJacob Faibussowitsch 100e6b6b59SJacob Faibussowitsch #include <limits> 110e6b6b59SJacob Faibussowitsch #include <deque> 120e6b6b59SJacob Faibussowitsch #include <vector> 130e6b6b59SJacob Faibussowitsch 140e6b6b59SJacob Faibussowitsch namespace Petsc { 150e6b6b59SJacob Faibussowitsch 160e6b6b59SJacob Faibussowitsch namespace device { 170e6b6b59SJacob Faibussowitsch 180e6b6b59SJacob Faibussowitsch template <typename T> 190e6b6b59SJacob Faibussowitsch class StreamBase { 200e6b6b59SJacob Faibussowitsch public: 210e6b6b59SJacob Faibussowitsch using id_type = int; 220e6b6b59SJacob Faibussowitsch using derived_type = T; 230e6b6b59SJacob Faibussowitsch 240e6b6b59SJacob Faibussowitsch static const id_type INVALID_ID; 250e6b6b59SJacob Faibussowitsch 260e6b6b59SJacob Faibussowitsch // needed so that dependent auto works, see veccupmimpl.h for a detailed discussion 270e6b6b59SJacob Faibussowitsch template <typename U = T> 280e6b6b59SJacob Faibussowitsch PETSC_NODISCARD auto get_stream() const noexcept PETSC_DECLTYPE_AUTO_RETURNS(static_cast<const U &>(*this).get_stream_()); 290e6b6b59SJacob Faibussowitsch 300e6b6b59SJacob Faibussowitsch PETSC_NODISCARD id_type get_id() const noexcept { return static_cast<const T &>(*this).get_id_(); } 310e6b6b59SJacob Faibussowitsch 320e6b6b59SJacob Faibussowitsch template <typename E> 330e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode record_event(E &&event) const noexcept { 340e6b6b59SJacob Faibussowitsch return static_cast<const T &>(*this).record_event_(std::forward<E>(event)); 350e6b6b59SJacob Faibussowitsch } 360e6b6b59SJacob Faibussowitsch 370e6b6b59SJacob Faibussowitsch template <typename E> 380e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode wait_for_event(E &&event) const noexcept { 390e6b6b59SJacob Faibussowitsch return static_cast<const T &>(*this).wait_for_(std::forward<E>(event)); 400e6b6b59SJacob Faibussowitsch } 410e6b6b59SJacob Faibussowitsch 420e6b6b59SJacob Faibussowitsch protected: 430e6b6b59SJacob Faibussowitsch constexpr StreamBase() noexcept = default; 440e6b6b59SJacob Faibussowitsch 450e6b6b59SJacob Faibussowitsch struct default_event_type { }; 460e6b6b59SJacob Faibussowitsch using default_stream_type = std::nullptr_t; 470e6b6b59SJacob Faibussowitsch 480e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr default_stream_type get_stream_() noexcept { return nullptr; } 490e6b6b59SJacob Faibussowitsch 500e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr id_type get_id_() noexcept { return 0; } 510e6b6b59SJacob Faibussowitsch 520e6b6b59SJacob Faibussowitsch template <typename U = T> 530e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr PetscErrorCode record_event_(const typename U::event_type &) noexcept { 540e6b6b59SJacob Faibussowitsch return 0; 550e6b6b59SJacob Faibussowitsch } 560e6b6b59SJacob Faibussowitsch 570e6b6b59SJacob Faibussowitsch template <typename U = T> 580e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static constexpr PetscErrorCode wait_for_(const typename U::event_type &) noexcept { 590e6b6b59SJacob Faibussowitsch return 0; 600e6b6b59SJacob Faibussowitsch } 610e6b6b59SJacob Faibussowitsch }; 620e6b6b59SJacob Faibussowitsch 630e6b6b59SJacob Faibussowitsch template <typename T> 640e6b6b59SJacob Faibussowitsch const typename StreamBase<T>::id_type StreamBase<T>::INVALID_ID = -1; 650e6b6b59SJacob Faibussowitsch 660e6b6b59SJacob Faibussowitsch struct DefaultStream : StreamBase<DefaultStream> { 670e6b6b59SJacob Faibussowitsch using stream_type = typename StreamBase<DefaultStream>::default_stream_type; 680e6b6b59SJacob Faibussowitsch using id_type = typename StreamBase<DefaultStream>::id_type; 690e6b6b59SJacob Faibussowitsch using event_type = typename StreamBase<DefaultStream>::default_event_type; 700e6b6b59SJacob Faibussowitsch }; 710e6b6b59SJacob Faibussowitsch 720e6b6b59SJacob Faibussowitsch } // namespace device 730e6b6b59SJacob Faibussowitsch 740e6b6b59SJacob Faibussowitsch namespace memory { 750e6b6b59SJacob Faibussowitsch 760e6b6b59SJacob Faibussowitsch namespace impl { 770e6b6b59SJacob Faibussowitsch 780e6b6b59SJacob Faibussowitsch // ========================================================================================== 790e6b6b59SJacob Faibussowitsch // MemoryChunk 800e6b6b59SJacob Faibussowitsch // 810e6b6b59SJacob Faibussowitsch // Represents a checked-out region of a MemoryBlock. Tracks the offset into the owning 820e6b6b59SJacob Faibussowitsch // MemoryBlock and its size/capacity 830e6b6b59SJacob Faibussowitsch // ========================================================================================== 840e6b6b59SJacob Faibussowitsch 850e6b6b59SJacob Faibussowitsch template <typename EventType> 860e6b6b59SJacob Faibussowitsch class MemoryChunk { 870e6b6b59SJacob Faibussowitsch public: 880e6b6b59SJacob Faibussowitsch using event_type = EventType; 890e6b6b59SJacob Faibussowitsch using size_type = std::size_t; 900e6b6b59SJacob Faibussowitsch 910e6b6b59SJacob Faibussowitsch MemoryChunk(size_type, size_type) noexcept; 920e6b6b59SJacob Faibussowitsch explicit MemoryChunk(size_type) noexcept; 930e6b6b59SJacob Faibussowitsch 940e6b6b59SJacob Faibussowitsch MemoryChunk(MemoryChunk &&) noexcept; 950e6b6b59SJacob Faibussowitsch MemoryChunk &operator=(MemoryChunk &&) noexcept; 960e6b6b59SJacob Faibussowitsch 970e6b6b59SJacob Faibussowitsch MemoryChunk(const MemoryChunk &) noexcept = delete; 980e6b6b59SJacob Faibussowitsch MemoryChunk &operator=(const MemoryChunk &) noexcept = delete; 990e6b6b59SJacob Faibussowitsch 1000e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type start() const noexcept { return start_; } 1010e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type size() const noexcept { return size_; } 1020e6b6b59SJacob Faibussowitsch // REVIEW ME: 1030e6b6b59SJacob Faibussowitsch // make this an actual field, normally each chunk shrinks_to_fit() on begin claimed, but in 1040e6b6b59SJacob Faibussowitsch // theory only the last chunk needs to do this 1050e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type capacity() const noexcept { return size_; } 1060e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type total_offset() const noexcept { return start() + size(); } 1070e6b6b59SJacob Faibussowitsch 1080e6b6b59SJacob Faibussowitsch template <typename U> 1090e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode release(const device::StreamBase<U> *) noexcept; 1100e6b6b59SJacob Faibussowitsch template <typename U> 1110e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode claim(const device::StreamBase<U> *, size_type, bool *, bool = false) noexcept; 1120e6b6b59SJacob Faibussowitsch template <typename U> 1130e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool can_claim(const device::StreamBase<U> *, size_type, bool) const noexcept; 1140e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode resize(size_type) noexcept; 1150e6b6b59SJacob Faibussowitsch 1160e6b6b59SJacob Faibussowitsch private: 1170e6b6b59SJacob Faibussowitsch // clang-format off 1180e6b6b59SJacob Faibussowitsch event_type event_{}; // event recorded when the chunk was released 1190e6b6b59SJacob Faibussowitsch bool open_ = true; // is this chunk open? 1200e6b6b59SJacob Faibussowitsch // id of the last stream to use the chunk, populated on release 1210e6b6b59SJacob Faibussowitsch int stream_id_ = device::DefaultStream::INVALID_ID; 1220e6b6b59SJacob Faibussowitsch size_type size_ = 0; // size of the chunk 1230e6b6b59SJacob Faibussowitsch const size_type start_ = 0; // offset from the start of the owning block 1240e6b6b59SJacob Faibussowitsch 1250e6b6b59SJacob Faibussowitsch // clang-format on 1260e6b6b59SJacob Faibussowitsch template <typename U> 1270e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool stream_compat_(const device::StreamBase<U> *) const noexcept; 1280e6b6b59SJacob Faibussowitsch }; 1290e6b6b59SJacob Faibussowitsch 1300e6b6b59SJacob Faibussowitsch // ========================================================================================== 1310e6b6b59SJacob Faibussowitsch // MemoryChunk - Private API 1320e6b6b59SJacob Faibussowitsch // ========================================================================================== 1330e6b6b59SJacob Faibussowitsch 1340e6b6b59SJacob Faibussowitsch // asks and answers the question: can this stream claim this chunk without serializing? 1350e6b6b59SJacob Faibussowitsch template <typename E> 1360e6b6b59SJacob Faibussowitsch template <typename U> 1370e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::stream_compat_(const device::StreamBase<U> *strm) const noexcept { 1380e6b6b59SJacob Faibussowitsch return (stream_id_ == strm->INVALID_ID) || (stream_id_ == strm->get_id()); 1390e6b6b59SJacob Faibussowitsch } 1400e6b6b59SJacob Faibussowitsch 1410e6b6b59SJacob Faibussowitsch // ========================================================================================== 1420e6b6b59SJacob Faibussowitsch // MemoryChunk - Public API 1430e6b6b59SJacob Faibussowitsch // ========================================================================================== 1440e6b6b59SJacob Faibussowitsch 1450e6b6b59SJacob Faibussowitsch template <typename E> 1460e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type start, size_type size) noexcept : size_(size), start_(start) { } 1470e6b6b59SJacob Faibussowitsch 1480e6b6b59SJacob Faibussowitsch template <typename E> 1490e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type size) noexcept : MemoryChunk(0, size) { } 1500e6b6b59SJacob Faibussowitsch 1510e6b6b59SJacob Faibussowitsch template <typename E> 1520e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(MemoryChunk<E> &&other) noexcept : 1530e6b6b59SJacob 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_)) { } 1540e6b6b59SJacob Faibussowitsch 1550e6b6b59SJacob Faibussowitsch template <typename E> 1560e6b6b59SJacob Faibussowitsch inline MemoryChunk<E> &MemoryChunk<E>::operator=(MemoryChunk<E> &&other) noexcept { 1570e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 1580e6b6b59SJacob Faibussowitsch if (this != &other) { 1590e6b6b59SJacob Faibussowitsch event_ = std::move(other.event_); 1600e6b6b59SJacob Faibussowitsch open_ = util::exchange(other.open_, false); 1610e6b6b59SJacob Faibussowitsch stream_id_ = util::exchange(other.stream_id_, device::DefaultStream::INVALID_ID); 1620e6b6b59SJacob Faibussowitsch size_ = util::exchange(other.size_, 0); 1630e6b6b59SJacob Faibussowitsch start_ = std::move(other.start_); 1640e6b6b59SJacob Faibussowitsch } 1650e6b6b59SJacob Faibussowitsch PetscFunctionReturn(*this); 1660e6b6b59SJacob Faibussowitsch } 1670e6b6b59SJacob Faibussowitsch 1680e6b6b59SJacob Faibussowitsch /* 1690e6b6b59SJacob Faibussowitsch MemoryChunk::release - release a chunk on a stream 1700e6b6b59SJacob Faibussowitsch 1710e6b6b59SJacob Faibussowitsch Input Parameter: 1720e6b6b59SJacob Faibussowitsch . stream - the stream to release the chunk with 1730e6b6b59SJacob Faibussowitsch 1740e6b6b59SJacob Faibussowitsch Notes: 1750e6b6b59SJacob Faibussowitsch Inserts a release operation on stream and records the state of stream at the time this 1760e6b6b59SJacob Faibussowitsch routine was called. 1770e6b6b59SJacob Faibussowitsch 1780e6b6b59SJacob Faibussowitsch Future allocation requests which attempt to claim the chunk on the same stream may re-acquire 1790e6b6b59SJacob Faibussowitsch the chunk without serialization. 1800e6b6b59SJacob Faibussowitsch 1810e6b6b59SJacob Faibussowitsch If another stream attempts to claim the chunk they must wait for the recorded event before 1820e6b6b59SJacob Faibussowitsch claiming the chunk. 1830e6b6b59SJacob Faibussowitsch */ 1840e6b6b59SJacob Faibussowitsch template <typename E> 1850e6b6b59SJacob Faibussowitsch template <typename U> 1860e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::release(const device::StreamBase<U> *stream) noexcept { 1870e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 1880e6b6b59SJacob Faibussowitsch open_ = true; 1890e6b6b59SJacob Faibussowitsch stream_id_ = stream->get_id(); 1900e6b6b59SJacob Faibussowitsch PetscCall(stream->record_event(event_)); 1910e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 1920e6b6b59SJacob Faibussowitsch } 1930e6b6b59SJacob Faibussowitsch 1940e6b6b59SJacob Faibussowitsch /* 1950e6b6b59SJacob Faibussowitsch MemoryChunk::claim - attempt to claim a particular chunk 1960e6b6b59SJacob Faibussowitsch 1970e6b6b59SJacob Faibussowitsch Input Parameters: 1980e6b6b59SJacob Faibussowitsch + stream - the stream on which to attempt to claim 1990e6b6b59SJacob Faibussowitsch . req_size - the requested size (in elements) to attempt to claim 2000e6b6b59SJacob Faibussowitsch - serialize - (optional, false) whether the claimant allows serialization 2010e6b6b59SJacob Faibussowitsch 2020e6b6b59SJacob Faibussowitsch Output Parameter: 2030e6b6b59SJacob Faibussowitsch . success - true if the chunk was claimed, false otherwise 2040e6b6b59SJacob Faibussowitsch */ 2050e6b6b59SJacob Faibussowitsch template <typename E> 2060e6b6b59SJacob Faibussowitsch template <typename U> 2070e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::claim(const device::StreamBase<U> *stream, size_type req_size, bool *success, bool serialize) noexcept { 2080e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 2090e6b6b59SJacob Faibussowitsch if ((*success = can_claim(stream, req_size, serialize))) { 2100e6b6b59SJacob Faibussowitsch if (serialize && !stream_compat_(stream)) PetscCall(stream->wait_for_event(event_)); 2110e6b6b59SJacob Faibussowitsch PetscCall(resize(req_size)); 2120e6b6b59SJacob Faibussowitsch open_ = false; 2130e6b6b59SJacob Faibussowitsch } 2140e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 2150e6b6b59SJacob Faibussowitsch } 2160e6b6b59SJacob Faibussowitsch 2170e6b6b59SJacob Faibussowitsch /* 2180e6b6b59SJacob Faibussowitsch MemoryChunk::can_claim - test whether a particular chunk can be claimed 2190e6b6b59SJacob Faibussowitsch 2200e6b6b59SJacob Faibussowitsch Input Parameters: 2210e6b6b59SJacob Faibussowitsch + stream - the stream on which to attempt to claim 2220e6b6b59SJacob Faibussowitsch . req_size - the requested size (in elements) to attempt to claim 2230e6b6b59SJacob Faibussowitsch - serialize - whether the claimant allows serialization 2240e6b6b59SJacob Faibussowitsch 2250e6b6b59SJacob Faibussowitsch Output: 2260e6b6b59SJacob Faibussowitsch . [return] - true if the chunk is claimable given the configuration, false otherwise 2270e6b6b59SJacob Faibussowitsch */ 2280e6b6b59SJacob Faibussowitsch template <typename E> 2290e6b6b59SJacob Faibussowitsch template <typename U> 2300e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::can_claim(const device::StreamBase<U> *stream, size_type req_size, bool serialize) const noexcept { 2310e6b6b59SJacob Faibussowitsch if (open_ && (req_size <= capacity())) { 2320e6b6b59SJacob Faibussowitsch // fully compatible 2330e6b6b59SJacob Faibussowitsch if (stream_compat_(stream)) return true; 2340e6b6b59SJacob Faibussowitsch // stream wasn't compatible, but could claim if we serialized 2350e6b6b59SJacob Faibussowitsch if (serialize) return true; 2360e6b6b59SJacob Faibussowitsch // incompatible stream and did not want to serialize 2370e6b6b59SJacob Faibussowitsch } 2380e6b6b59SJacob Faibussowitsch return false; 2390e6b6b59SJacob Faibussowitsch } 2400e6b6b59SJacob Faibussowitsch 2410e6b6b59SJacob Faibussowitsch /* 2420e6b6b59SJacob Faibussowitsch MemoryChunk::resize - grow a chunk to new size 2430e6b6b59SJacob Faibussowitsch 2440e6b6b59SJacob Faibussowitsch Input Parameter: 2450e6b6b59SJacob Faibussowitsch . newsize - the new size Requested 2460e6b6b59SJacob Faibussowitsch 2470e6b6b59SJacob Faibussowitsch Notes: 2480e6b6b59SJacob Faibussowitsch newsize cannot be larger than capacity 2490e6b6b59SJacob Faibussowitsch */ 2500e6b6b59SJacob Faibussowitsch template <typename E> 2510e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::resize(size_type newsize) noexcept { 2520e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 2530e6b6b59SJacob Faibussowitsch PetscAssert(newsize <= capacity(), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "New size %zu larger than capacity %zu", newsize, capacity()); 2540e6b6b59SJacob Faibussowitsch size_ = newsize; 2550e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 2560e6b6b59SJacob Faibussowitsch } 2570e6b6b59SJacob Faibussowitsch 2580e6b6b59SJacob Faibussowitsch // ========================================================================================== 2590e6b6b59SJacob Faibussowitsch // MemoryBlock 2600e6b6b59SJacob Faibussowitsch // 2610e6b6b59SJacob Faibussowitsch // A "memory block" manager, which owns the pointer to a particular memory range. Retrieving 2620e6b6b59SJacob Faibussowitsch // and restoring a block is thread-safe (so may be used by multiple device streams). 2630e6b6b59SJacob Faibussowitsch // ========================================================================================== 2640e6b6b59SJacob Faibussowitsch 2650e6b6b59SJacob Faibussowitsch template <typename T, typename AllocatorType, typename StreamType> 2660e6b6b59SJacob Faibussowitsch class MemoryBlock { 2670e6b6b59SJacob Faibussowitsch public: 2680e6b6b59SJacob Faibussowitsch using value_type = T; 2690e6b6b59SJacob Faibussowitsch using allocator_type = AllocatorType; 2700e6b6b59SJacob Faibussowitsch using stream_type = StreamType; 2710e6b6b59SJacob Faibussowitsch using event_type = typename stream_type::event_type; 2720e6b6b59SJacob Faibussowitsch using chunk_type = MemoryChunk<event_type>; 2730e6b6b59SJacob Faibussowitsch using size_type = typename chunk_type::size_type; 2740e6b6b59SJacob Faibussowitsch using chunk_list_type = std::vector<chunk_type>; 2750e6b6b59SJacob Faibussowitsch 2760e6b6b59SJacob Faibussowitsch template <typename U> 2770e6b6b59SJacob Faibussowitsch MemoryBlock(allocator_type *, size_type, const device::StreamBase<U> *) noexcept; 2780e6b6b59SJacob Faibussowitsch 2790e6b6b59SJacob Faibussowitsch ~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value); 2800e6b6b59SJacob Faibussowitsch 2810e6b6b59SJacob Faibussowitsch MemoryBlock(MemoryBlock &&) noexcept; 2820e6b6b59SJacob Faibussowitsch MemoryBlock &operator=(MemoryBlock &&) noexcept; 2830e6b6b59SJacob Faibussowitsch 2840e6b6b59SJacob Faibussowitsch // memory blocks are not copyable 2850e6b6b59SJacob Faibussowitsch MemoryBlock(const MemoryBlock &) = delete; 2860e6b6b59SJacob Faibussowitsch MemoryBlock &operator=(const MemoryBlock &) = delete; 2870e6b6b59SJacob Faibussowitsch 2880e6b6b59SJacob Faibussowitsch /* --- actual functions --- */ 2890e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_allocate_chunk(size_type, T **, const stream_type *, bool *) noexcept; 2900e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_deallocate_chunk(T **, const stream_type *, bool *) noexcept; 2910e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode try_find_chunk(const T *, chunk_type **) noexcept; 2920e6b6b59SJacob Faibussowitsch PETSC_NODISCARD bool owns_pointer(const T *) const noexcept; 2930e6b6b59SJacob Faibussowitsch 2940e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type size() const noexcept { return size_; } 2950e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type bytes() const noexcept { return sizeof(value_type) * size(); } 2960e6b6b59SJacob Faibussowitsch PETSC_NODISCARD size_type num_chunks() const noexcept { return chunks_.size(); } 2970e6b6b59SJacob Faibussowitsch 2980e6b6b59SJacob Faibussowitsch private: 2990e6b6b59SJacob Faibussowitsch value_type *mem_{}; 3000e6b6b59SJacob Faibussowitsch allocator_type *allocator_{}; 3010e6b6b59SJacob Faibussowitsch size_type size_{}; 3020e6b6b59SJacob Faibussowitsch chunk_list_type chunks_{}; 3030e6b6b59SJacob Faibussowitsch 3040e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode clear_(const stream_type *) noexcept; 3050e6b6b59SJacob Faibussowitsch }; 3060e6b6b59SJacob Faibussowitsch 3070e6b6b59SJacob Faibussowitsch // ========================================================================================== 3080e6b6b59SJacob Faibussowitsch // MemoryBlock - Private API 3090e6b6b59SJacob Faibussowitsch // ========================================================================================== 3100e6b6b59SJacob Faibussowitsch 3110e6b6b59SJacob Faibussowitsch // clear the memory block, called from destructors and move assignment/construction 3120e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3130e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode MemoryBlock<T, A, S>::clear_(const stream_type *stream) noexcept { 3140e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3150e6b6b59SJacob Faibussowitsch if (PetscLikely(mem_)) { 3160e6b6b59SJacob Faibussowitsch PetscCall(allocator_->deallocate(mem_, stream)); 3170e6b6b59SJacob Faibussowitsch mem_ = nullptr; 3180e6b6b59SJacob Faibussowitsch } 3190e6b6b59SJacob Faibussowitsch size_ = 0; 3200e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.clear()); 3210e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 3220e6b6b59SJacob Faibussowitsch } 3230e6b6b59SJacob Faibussowitsch 3240e6b6b59SJacob Faibussowitsch // ========================================================================================== 3250e6b6b59SJacob Faibussowitsch // MemoryBlock - Public API 3260e6b6b59SJacob Faibussowitsch // ========================================================================================== 3270e6b6b59SJacob Faibussowitsch 3280e6b6b59SJacob Faibussowitsch // default constructor, allocates memory immediately 3290e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3300e6b6b59SJacob Faibussowitsch template <typename U> 331*127bc8d3SJacob Faibussowitsch MemoryBlock<T, A, S>::MemoryBlock(allocator_type *alloc, size_type s, const device::StreamBase<U> *stream) noexcept : allocator_(alloc), size_(s) { 3320e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3330e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, alloc->allocate(&mem_, s, stream)); 3340e6b6b59SJacob Faibussowitsch PetscAssertAbort(mem_, PETSC_COMM_SELF, PETSC_ERR_MEM, "Failed to allocate memory block of size %zu", s); 3350e6b6b59SJacob Faibussowitsch PetscFunctionReturnVoid(); 3360e6b6b59SJacob Faibussowitsch } 3370e6b6b59SJacob Faibussowitsch 3380e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3390e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value) { 3400e6b6b59SJacob Faibussowitsch stream_type stream; 3410e6b6b59SJacob Faibussowitsch 3420e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3430e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, clear_(&stream)); 3440e6b6b59SJacob Faibussowitsch PetscFunctionReturnVoid(); 3450e6b6b59SJacob Faibussowitsch } 3460e6b6b59SJacob Faibussowitsch 3470e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3480e6b6b59SJacob 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_)) { } 3490e6b6b59SJacob Faibussowitsch 3500e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3510e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S> &MemoryBlock<T, A, S>::operator=(MemoryBlock &&other) noexcept { 3520e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3530e6b6b59SJacob Faibussowitsch if (this != &other) { 3540e6b6b59SJacob Faibussowitsch stream_type stream; 3550e6b6b59SJacob Faibussowitsch 3560e6b6b59SJacob Faibussowitsch PetscCallAbort(PETSC_COMM_SELF, clear_(&stream)); 3570e6b6b59SJacob Faibussowitsch mem_ = util::exchange(other.mem_, nullptr); 3580e6b6b59SJacob Faibussowitsch allocator_ = other.allocator_; 3590e6b6b59SJacob Faibussowitsch size_ = util::exchange(other.size_, 0); 3600e6b6b59SJacob Faibussowitsch chunks_ = std::move(other.chunks_); 3610e6b6b59SJacob Faibussowitsch } 3620e6b6b59SJacob Faibussowitsch PetscFunctionReturn(*this); 3630e6b6b59SJacob Faibussowitsch } 3640e6b6b59SJacob Faibussowitsch 3650e6b6b59SJacob Faibussowitsch /* 3660e6b6b59SJacob Faibussowitsch MemoryBock::owns_pointer - returns true if this block owns a pointer, false otherwise 3670e6b6b59SJacob Faibussowitsch */ 3680e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3690e6b6b59SJacob Faibussowitsch inline bool MemoryBlock<T, A, S>::owns_pointer(const T *ptr) const noexcept { 3700e6b6b59SJacob Faibussowitsch // each pool is linear in memory, so it suffices to check the bounds 3710e6b6b59SJacob Faibussowitsch return (ptr >= mem_) && (ptr < std::next(mem_, size())); 3720e6b6b59SJacob Faibussowitsch } 3730e6b6b59SJacob Faibussowitsch 3740e6b6b59SJacob Faibussowitsch /* 3750e6b6b59SJacob Faibussowitsch MemoryBlock::try_allocate_chunk - try to get a chunk from this MemoryBlock 3760e6b6b59SJacob Faibussowitsch 3770e6b6b59SJacob Faibussowitsch Input Parameters: 3780e6b6b59SJacob Faibussowitsch + req_size - the requested size of the allocation (in elements) 3790e6b6b59SJacob Faibussowitsch . ptr - ptr to fill 3800e6b6b59SJacob Faibussowitsch - stream - stream to fill the pointer on 3810e6b6b59SJacob Faibussowitsch 3820e6b6b59SJacob Faibussowitsch Output Parameter: 3830e6b6b59SJacob Faibussowitsch . success - true if chunk was gotten, false otherwise 3840e6b6b59SJacob Faibussowitsch 3850e6b6b59SJacob Faibussowitsch Notes: 3860e6b6b59SJacob Faibussowitsch If the current memory could not satisfy the memory request, ptr is unchanged 3870e6b6b59SJacob Faibussowitsch */ 3880e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 3890e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_allocate_chunk(size_type req_size, T **ptr, const stream_type *stream, bool *success) noexcept { 3900e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3910e6b6b59SJacob Faibussowitsch *success = false; 3920e6b6b59SJacob Faibussowitsch if (req_size <= size()) { 3930e6b6b59SJacob Faibussowitsch const auto try_create_chunk = [&]() { 3940e6b6b59SJacob Faibussowitsch const auto was_empty = chunks_.empty(); 3950e6b6b59SJacob Faibussowitsch const auto block_alloced = was_empty ? 0 : chunks_.back().total_offset(); 3960e6b6b59SJacob Faibussowitsch 3970e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 3980e6b6b59SJacob Faibussowitsch if (block_alloced + req_size <= size()) { 3990e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.emplace_back(block_alloced, req_size)); 4000e6b6b59SJacob Faibussowitsch PetscCall(chunks_.back().claim(stream, req_size, success)); 4010e6b6b59SJacob Faibussowitsch *ptr = mem_ + block_alloced; 4020e6b6b59SJacob 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()); 4030e6b6b59SJacob Faibussowitsch } 4040e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 4050e6b6b59SJacob Faibussowitsch }; 4060e6b6b59SJacob Faibussowitsch const auto try_find_open_chunk = [&](bool serialize = false) { 4070e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 4080e6b6b59SJacob Faibussowitsch for (auto &chunk : chunks_) { 4090e6b6b59SJacob Faibussowitsch PetscCall(chunk.claim(stream, req_size, success, serialize)); 4100e6b6b59SJacob Faibussowitsch if (*success) { 4110e6b6b59SJacob Faibussowitsch *ptr = mem_ + chunk.start(); 4120e6b6b59SJacob Faibussowitsch break; 4130e6b6b59SJacob Faibussowitsch } 4140e6b6b59SJacob Faibussowitsch } 4150e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 4160e6b6b59SJacob Faibussowitsch }; 4170e6b6b59SJacob Faibussowitsch 4180e6b6b59SJacob Faibussowitsch // search previously distributed chunks, but only claim one if it is on the same stream 4190e6b6b59SJacob Faibussowitsch // as us 4200e6b6b59SJacob Faibussowitsch PetscCall(try_find_open_chunk()); 4210e6b6b59SJacob Faibussowitsch 4220e6b6b59SJacob Faibussowitsch // if we are here we couldn't reuse one of our own chunks so check first if the pool 4230e6b6b59SJacob Faibussowitsch // has room for a new one 4240e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_create_chunk()); 4250e6b6b59SJacob Faibussowitsch 4260e6b6b59SJacob Faibussowitsch // try pruning dead chunks off the back, note we do this regardless of whether we are 4270e6b6b59SJacob Faibussowitsch // successful 4280e6b6b59SJacob Faibussowitsch while (chunks_.back().can_claim(stream, 0, false)) { 4290e6b6b59SJacob Faibussowitsch PetscCallCXX(chunks_.pop_back()); 4300e6b6b59SJacob Faibussowitsch if (chunks_.empty()) { 4310e6b6b59SJacob Faibussowitsch // if chunks are empty it implies we have managed to claim (and subsequently destroy) 4320e6b6b59SJacob Faibussowitsch // our own chunk twice! something has gone wrong 4330e6b6b59SJacob 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()); 4340e6b6b59SJacob Faibussowitsch break; 4350e6b6b59SJacob Faibussowitsch } 4360e6b6b59SJacob Faibussowitsch } 4370e6b6b59SJacob Faibussowitsch 4380e6b6b59SJacob Faibussowitsch // if previously unsuccessful see if enough space has opened up due to pruning. note that 4390e6b6b59SJacob Faibussowitsch // if the chunk list was emptied from the pruning this call must succeed in allocating a 4400e6b6b59SJacob Faibussowitsch // chunk, otherwise something is wrong 4410e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_create_chunk()); 4420e6b6b59SJacob Faibussowitsch 4430e6b6b59SJacob Faibussowitsch // last resort, iterate over all chunks and see if we can steal one by waiting on the 4440e6b6b59SJacob Faibussowitsch // current owner to finish using it 4450e6b6b59SJacob Faibussowitsch if (!*success) PetscCall(try_find_open_chunk(true)); 4460e6b6b59SJacob Faibussowitsch 4470e6b6b59SJacob Faibussowitsch // sets memory to NaN or infinity depending on the type to catch out uninitialized memory 4480e6b6b59SJacob Faibussowitsch // accesses. 4490e6b6b59SJacob Faibussowitsch if (PetscDefined(USE_DEBUG) && *success) PetscCall(allocator_->set_canary(*ptr, req_size, stream)); 4500e6b6b59SJacob Faibussowitsch } 4510e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 4520e6b6b59SJacob Faibussowitsch } 4530e6b6b59SJacob Faibussowitsch 4540e6b6b59SJacob Faibussowitsch /* 4550e6b6b59SJacob Faibussowitsch MemoryBlock::try_deallocate_chunk - try to restore a chunk to this MemoryBlock 4560e6b6b59SJacob Faibussowitsch 4570e6b6b59SJacob Faibussowitsch Input Parameters: 4580e6b6b59SJacob Faibussowitsch + ptr - ptr to restore 4590e6b6b59SJacob Faibussowitsch - stream - stream to restore the pointer on 4600e6b6b59SJacob Faibussowitsch 4610e6b6b59SJacob Faibussowitsch Output Parameter: 4620e6b6b59SJacob Faibussowitsch . success - true if chunk was restored, false otherwise 4630e6b6b59SJacob Faibussowitsch 4640e6b6b59SJacob Faibussowitsch Notes: 4650e6b6b59SJacob Faibussowitsch ptr is set to nullptr on successful restore, and is unchanged otherwise. If the ptr is owned 4660e6b6b59SJacob Faibussowitsch by this MemoryBlock then it is restored on stream. The same stream may recieve ptr again 4670e6b6b59SJacob Faibussowitsch without synchronization, but other streams may not do so until either serializing or the 4680e6b6b59SJacob Faibussowitsch stream is idle again. 4690e6b6b59SJacob Faibussowitsch */ 4700e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 4710e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_deallocate_chunk(T **ptr, const stream_type *stream, bool *success) noexcept { 4720e6b6b59SJacob Faibussowitsch chunk_type *chunk = nullptr; 4730e6b6b59SJacob Faibussowitsch 4740e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 4750e6b6b59SJacob Faibussowitsch PetscCall(try_find_chunk(*ptr, &chunk)); 4760e6b6b59SJacob Faibussowitsch if (chunk) { 4770e6b6b59SJacob Faibussowitsch PetscCall(chunk->release(stream)); 4780e6b6b59SJacob Faibussowitsch *ptr = nullptr; 4790e6b6b59SJacob Faibussowitsch *success = true; 4800e6b6b59SJacob Faibussowitsch } else { 4810e6b6b59SJacob Faibussowitsch *success = false; 4820e6b6b59SJacob Faibussowitsch } 4830e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 4840e6b6b59SJacob Faibussowitsch } 4850e6b6b59SJacob Faibussowitsch 4860e6b6b59SJacob Faibussowitsch /* 4870e6b6b59SJacob Faibussowitsch MemoryBlock::try_find_chunk - try to find the chunk which owns ptr 4880e6b6b59SJacob Faibussowitsch 4890e6b6b59SJacob Faibussowitsch Input Parameter: 4900e6b6b59SJacob Faibussowitsch . ptr - the pointer to lookk for 4910e6b6b59SJacob Faibussowitsch 4920e6b6b59SJacob Faibussowitsch Output Parameter: 4930e6b6b59SJacob Faibussowitsch . ret_chunk - pointer to the owning chunk or nullptr if not found 4940e6b6b59SJacob Faibussowitsch */ 4950e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S> 4960e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_find_chunk(const T *ptr, chunk_type **ret_chunk) noexcept { 4970e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 4980e6b6b59SJacob Faibussowitsch *ret_chunk = nullptr; 4990e6b6b59SJacob Faibussowitsch if (owns_pointer(ptr)) { 5000e6b6b59SJacob Faibussowitsch const auto offset = static_cast<size_type>(ptr - mem_); 5010e6b6b59SJacob Faibussowitsch 5020e6b6b59SJacob Faibussowitsch for (auto &chunk : chunks_) { 5030e6b6b59SJacob Faibussowitsch if (chunk.start() == offset) { 5040e6b6b59SJacob Faibussowitsch *ret_chunk = &chunk; 5050e6b6b59SJacob Faibussowitsch break; 5060e6b6b59SJacob Faibussowitsch } 5070e6b6b59SJacob Faibussowitsch } 5080e6b6b59SJacob Faibussowitsch 5090e6b6b59SJacob 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()))); 5100e6b6b59SJacob Faibussowitsch } 5110e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5120e6b6b59SJacob Faibussowitsch } 5130e6b6b59SJacob Faibussowitsch 5140e6b6b59SJacob Faibussowitsch namespace detail { 5150e6b6b59SJacob Faibussowitsch 5160e6b6b59SJacob Faibussowitsch template <typename T> 5170e6b6b59SJacob Faibussowitsch struct real_type { 5180e6b6b59SJacob Faibussowitsch using type = T; 5190e6b6b59SJacob Faibussowitsch }; 5200e6b6b59SJacob Faibussowitsch 5210e6b6b59SJacob Faibussowitsch template <> 5220e6b6b59SJacob Faibussowitsch struct real_type<PetscScalar> { 5230e6b6b59SJacob Faibussowitsch using type = PetscReal; 5240e6b6b59SJacob Faibussowitsch }; 5250e6b6b59SJacob Faibussowitsch 5260e6b6b59SJacob Faibussowitsch } // namespace detail 5270e6b6b59SJacob Faibussowitsch 5280e6b6b59SJacob Faibussowitsch template <typename T> 5290e6b6b59SJacob Faibussowitsch struct SegmentedMemoryPoolAllocatorBase { 5300e6b6b59SJacob Faibussowitsch using value_type = T; 5310e6b6b59SJacob Faibussowitsch using size_type = std::size_t; 5320e6b6b59SJacob Faibussowitsch using real_value_type = typename detail::real_type<T>::type; 5330e6b6b59SJacob Faibussowitsch 5340e6b6b59SJacob Faibussowitsch template <typename U> 5350e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode allocate(value_type **, size_type, const device::StreamBase<U> *) noexcept; 5360e6b6b59SJacob Faibussowitsch template <typename U> 5370e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode deallocate(value_type *, const device::StreamBase<U> *) noexcept; 5380e6b6b59SJacob Faibussowitsch template <typename U> 5390e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode zero(value_type *, size_type, const device::StreamBase<U> *) noexcept; 5400e6b6b59SJacob Faibussowitsch template <typename U> 5410e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode uninitialized_copy(value_type *, const value_type *, size_type, const device::StreamBase<U> *) noexcept; 5420e6b6b59SJacob Faibussowitsch template <typename U> 5430e6b6b59SJacob Faibussowitsch PETSC_NODISCARD static PetscErrorCode set_canary(value_type *, size_type, const device::StreamBase<U> *) noexcept; 5440e6b6b59SJacob Faibussowitsch }; 5450e6b6b59SJacob Faibussowitsch 5460e6b6b59SJacob Faibussowitsch template <typename T> 5470e6b6b59SJacob Faibussowitsch template <typename U> 5480e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::allocate(value_type **ptr, size_type n, const device::StreamBase<U> *) noexcept { 5490e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 5500e6b6b59SJacob Faibussowitsch PetscCall(PetscMalloc1(n, ptr)); 5510e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5520e6b6b59SJacob Faibussowitsch } 5530e6b6b59SJacob Faibussowitsch 5540e6b6b59SJacob Faibussowitsch template <typename T> 5550e6b6b59SJacob Faibussowitsch template <typename U> 5560e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::deallocate(value_type *ptr, const device::StreamBase<U> *) noexcept { 5570e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 5580e6b6b59SJacob Faibussowitsch PetscCall(PetscFree(ptr)); 5590e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5600e6b6b59SJacob Faibussowitsch } 5610e6b6b59SJacob Faibussowitsch 5620e6b6b59SJacob Faibussowitsch template <typename T> 5630e6b6b59SJacob Faibussowitsch template <typename U> 5640e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::zero(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept { 5650e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 5660e6b6b59SJacob Faibussowitsch PetscCall(PetscArrayzero(ptr, n)); 5670e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5680e6b6b59SJacob Faibussowitsch } 5690e6b6b59SJacob Faibussowitsch 5700e6b6b59SJacob Faibussowitsch template <typename T> 5710e6b6b59SJacob Faibussowitsch template <typename U> 5720e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::uninitialized_copy(value_type *dest, const value_type *src, size_type n, const device::StreamBase<U> *) noexcept { 5730e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 5740e6b6b59SJacob Faibussowitsch PetscCall(PetscArraycpy(dest, src, n)); 5750e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5760e6b6b59SJacob Faibussowitsch } 5770e6b6b59SJacob Faibussowitsch 5780e6b6b59SJacob Faibussowitsch template <typename T> 5790e6b6b59SJacob Faibussowitsch template <typename U> 5800e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::set_canary(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept { 5810e6b6b59SJacob Faibussowitsch using limit_type = std::numeric_limits<real_value_type>; 5820e6b6b59SJacob Faibussowitsch constexpr value_type canary = limit_type::has_signaling_NaN ? limit_type::signaling_NaN() : limit_type::max(); 5830e6b6b59SJacob Faibussowitsch 5840e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 5850e6b6b59SJacob Faibussowitsch for (size_type i = 0; i < n; ++i) ptr[i] = canary; 5860e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 5870e6b6b59SJacob Faibussowitsch } 5880e6b6b59SJacob Faibussowitsch 5890e6b6b59SJacob Faibussowitsch } // namespace impl 5900e6b6b59SJacob Faibussowitsch 5910e6b6b59SJacob Faibussowitsch // ========================================================================================== 5920e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool 5930e6b6b59SJacob Faibussowitsch // ========================================================================================== 5940e6b6b59SJacob Faibussowitsch 5950e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType = device::DefaultStream, typename AllocType = impl::SegmentedMemoryPoolAllocatorBase<MemType>, std::size_t DefaultChunkSize = 256> 5960e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool; 5970e6b6b59SJacob Faibussowitsch 5980e6b6b59SJacob Faibussowitsch // The actual memory pool class. It is in essence just a wrapper for a list of MemoryBlocks. 5990e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6000e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool : public RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>> { 6010e6b6b59SJacob Faibussowitsch public: 6020e6b6b59SJacob Faibussowitsch using value_type = MemType; 6030e6b6b59SJacob Faibussowitsch using stream_type = StreamType; 6040e6b6b59SJacob Faibussowitsch using allocator_type = AllocType; 6050e6b6b59SJacob Faibussowitsch using block_type = impl::MemoryBlock<value_type, allocator_type, stream_type>; 6060e6b6b59SJacob Faibussowitsch using pool_type = std::deque<block_type>; 6070e6b6b59SJacob Faibussowitsch using size_type = typename block_type::size_type; 6080e6b6b59SJacob Faibussowitsch 6090e6b6b59SJacob Faibussowitsch explicit SegmentedMemoryPool(AllocType = AllocType{}, std::size_t = DefaultChunkSize) noexcept(std::is_nothrow_default_constructible<pool_type>::value); 6100e6b6b59SJacob Faibussowitsch 6110e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode allocate(PetscInt, value_type **, const stream_type *) noexcept; 6120e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode deallocate(value_type **, const stream_type *) noexcept; 6130e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode reallocate(PetscInt, value_type **, const stream_type *) noexcept; 6140e6b6b59SJacob Faibussowitsch 6150e6b6b59SJacob Faibussowitsch private: 6160e6b6b59SJacob Faibussowitsch pool_type pool_; 6170e6b6b59SJacob Faibussowitsch allocator_type allocator_; 6180e6b6b59SJacob Faibussowitsch size_type chunk_size_; 6190e6b6b59SJacob Faibussowitsch 6200e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode make_block_(size_type, const stream_type *) noexcept; 6210e6b6b59SJacob Faibussowitsch 6220e6b6b59SJacob Faibussowitsch friend class RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>>; 6230e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode register_finalize_(const stream_type *) noexcept; 6240e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode finalize_() noexcept; 6250e6b6b59SJacob Faibussowitsch }; 6260e6b6b59SJacob Faibussowitsch 6270e6b6b59SJacob Faibussowitsch // ========================================================================================== 6280e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Private API 6290e6b6b59SJacob Faibussowitsch // ========================================================================================== 6300e6b6b59SJacob Faibussowitsch 6310e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6320e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::make_block_(size_type size, const stream_type *stream) noexcept { 6330e6b6b59SJacob Faibussowitsch const auto block_size = std::max(size, chunk_size_); 6340e6b6b59SJacob Faibussowitsch 6350e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 6360e6b6b59SJacob Faibussowitsch PetscCallCXX(pool_.emplace_back(&allocator_, block_size, stream)); 6370e6b6b59SJacob Faibussowitsch PetscCall(PetscInfo(nullptr, "Allocated new block of size %zu, total %zu blocks\n", block_size, pool_.size())); 6380e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 6390e6b6b59SJacob Faibussowitsch } 6400e6b6b59SJacob Faibussowitsch 6410e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6420e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::register_finalize_(const stream_type *stream) noexcept { 6430e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 6440e6b6b59SJacob Faibussowitsch PetscCall(make_block_(chunk_size_, stream)); 6450e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 6460e6b6b59SJacob Faibussowitsch } 6470e6b6b59SJacob Faibussowitsch 6480e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6490e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::finalize_() noexcept { 6500e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 6510e6b6b59SJacob Faibussowitsch PetscCallCXX(pool_.clear()); 6520e6b6b59SJacob Faibussowitsch chunk_size_ = DefaultChunkSize; 6530e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 6540e6b6b59SJacob Faibussowitsch } 6550e6b6b59SJacob Faibussowitsch 6560e6b6b59SJacob Faibussowitsch // ========================================================================================== 6570e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Public API 6580e6b6b59SJacob Faibussowitsch // ========================================================================================== 6590e6b6b59SJacob Faibussowitsch 6600e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6610e6b6b59SJacob Faibussowitsch inline SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::SegmentedMemoryPool(AllocType alloc, std::size_t size) noexcept(std::is_nothrow_default_constructible<pool_type>::value) : 6620e6b6b59SJacob Faibussowitsch allocator_(std::move(alloc)), chunk_size_(size) { } 6630e6b6b59SJacob Faibussowitsch 6640e6b6b59SJacob Faibussowitsch /* 6650e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::allocate - get an allocation from the memory pool 6660e6b6b59SJacob Faibussowitsch 6670e6b6b59SJacob Faibussowitsch Input Parameters: 6680e6b6b59SJacob Faibussowitsch + req_size - size (in elements) to get 6690e6b6b59SJacob Faibussowitsch . ptr - the pointer to hold the allocation 6700e6b6b59SJacob Faibussowitsch - stream - the stream on which to get the allocation 6710e6b6b59SJacob Faibussowitsch 6720e6b6b59SJacob Faibussowitsch Output Parameter: 6730e6b6b59SJacob Faibussowitsch . ptr - the pointer holding the allocation 6740e6b6b59SJacob Faibussowitsch 6750e6b6b59SJacob Faibussowitsch Notes: 6760e6b6b59SJacob Faibussowitsch req_size cannot be negative. If req_size if zero, ptr is set to nullptr 6770e6b6b59SJacob Faibussowitsch */ 6780e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 6790e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::allocate(PetscInt req_size, value_type **ptr, const stream_type *stream) noexcept { 6800e6b6b59SJacob Faibussowitsch const auto size = static_cast<size_type>(req_size); 6810e6b6b59SJacob Faibussowitsch auto found = false; 6820e6b6b59SJacob Faibussowitsch 6830e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 6840e6b6b59SJacob Faibussowitsch PetscAssert(req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", req_size); 6850e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 2); 6860e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 3); 6870e6b6b59SJacob Faibussowitsch *ptr = nullptr; 6880e6b6b59SJacob Faibussowitsch if (!req_size) PetscFunctionReturn(0); 6890e6b6b59SJacob Faibussowitsch PetscCall(this->register_finalize(PETSC_COMM_SELF, stream)); 6900e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 6910e6b6b59SJacob Faibussowitsch PetscCall(block.try_allocate_chunk(size, ptr, stream, &found)); 6920e6b6b59SJacob Faibussowitsch if (PetscLikely(found)) PetscFunctionReturn(0); 6930e6b6b59SJacob Faibussowitsch } 6940e6b6b59SJacob Faibussowitsch 6950e6b6b59SJacob 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)); 6960e6b6b59SJacob Faibussowitsch // if we are here we couldn't find an open block in the pool, so make a new block 6970e6b6b59SJacob Faibussowitsch PetscCall(make_block_(size, stream)); 6980e6b6b59SJacob Faibussowitsch // and assign it 6990e6b6b59SJacob Faibussowitsch PetscCall(pool_.back().try_allocate_chunk(size, ptr, stream, &found)); 7000e6b6b59SJacob 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()); 7010e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 7020e6b6b59SJacob Faibussowitsch } 7030e6b6b59SJacob Faibussowitsch 7040e6b6b59SJacob Faibussowitsch /* 7050e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::deallocate - release a pointer back to the memory pool 7060e6b6b59SJacob Faibussowitsch 7070e6b6b59SJacob Faibussowitsch Input Parameters: 7080e6b6b59SJacob Faibussowitsch + ptr - the pointer to release 7090e6b6b59SJacob Faibussowitsch - stream - the stream to release it on 7100e6b6b59SJacob Faibussowitsch 7110e6b6b59SJacob Faibussowitsch Notes: 7120e6b6b59SJacob Faibussowitsch If ptr is not owned by the pool it is unchanged. 7130e6b6b59SJacob Faibussowitsch */ 7140e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 7150e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::deallocate(value_type **ptr, const stream_type *stream) noexcept { 7160e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 7170e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 1); 7180e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 2); 7190e6b6b59SJacob Faibussowitsch // nobody owns a nullptr, and if they do then they have bigger problems 7200e6b6b59SJacob Faibussowitsch if (!*ptr) PetscFunctionReturn(0); 7210e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 7220e6b6b59SJacob Faibussowitsch auto found = false; 7230e6b6b59SJacob Faibussowitsch 7240e6b6b59SJacob Faibussowitsch PetscCall(block.try_deallocate_chunk(ptr, stream, &found)); 7250e6b6b59SJacob Faibussowitsch if (PetscLikely(found)) break; 7260e6b6b59SJacob Faibussowitsch } 7270e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 7280e6b6b59SJacob Faibussowitsch } 7290e6b6b59SJacob Faibussowitsch 7300e6b6b59SJacob Faibussowitsch /* 7310e6b6b59SJacob Faibussowitsch SegmentedMemoryPool::reallocate - Resize an allocated buffer 7320e6b6b59SJacob Faibussowitsch 7330e6b6b59SJacob Faibussowitsch Input Parameters: 7340e6b6b59SJacob Faibussowitsch + new_req_size - the new buffer size 7350e6b6b59SJacob Faibussowitsch . ptr - pointer to the buffer 7360e6b6b59SJacob Faibussowitsch - stream - stream to resize with 7370e6b6b59SJacob Faibussowitsch 7380e6b6b59SJacob Faibussowitsch Ouput Parameter: 7390e6b6b59SJacob Faibussowitsch . ptr - pointer to the new region 7400e6b6b59SJacob Faibussowitsch 7410e6b6b59SJacob Faibussowitsch Notes: 7420e6b6b59SJacob Faibussowitsch ptr must have been allocated by the pool. 7430e6b6b59SJacob Faibussowitsch 7440e6b6b59SJacob Faibussowitsch It's OK to shrink the buffer, even down to 0 (in which case it is just deallocated). 7450e6b6b59SJacob Faibussowitsch */ 7460e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize> 7470e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::reallocate(PetscInt new_req_size, value_type **ptr, const stream_type *stream) noexcept { 7480e6b6b59SJacob Faibussowitsch using chunk_type = typename block_type::chunk_type; 7490e6b6b59SJacob Faibussowitsch 7500e6b6b59SJacob Faibussowitsch const auto new_size = static_cast<size_type>(new_req_size); 7510e6b6b59SJacob Faibussowitsch const auto old_ptr = *ptr; 7520e6b6b59SJacob Faibussowitsch chunk_type *chunk = nullptr; 7530e6b6b59SJacob Faibussowitsch 7540e6b6b59SJacob Faibussowitsch PetscFunctionBegin; 7550e6b6b59SJacob Faibussowitsch PetscAssert(new_req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", new_req_size); 7560e6b6b59SJacob Faibussowitsch PetscValidPointer(ptr, 2); 7570e6b6b59SJacob Faibussowitsch PetscValidPointer(stream, 3); 7580e6b6b59SJacob Faibussowitsch 7590e6b6b59SJacob Faibussowitsch // if reallocating to zero, just free 7600e6b6b59SJacob Faibussowitsch if (PetscUnlikely(new_size == 0)) { 7610e6b6b59SJacob Faibussowitsch PetscCall(deallocate(ptr, stream)); 7620e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 7630e6b6b59SJacob Faibussowitsch } 7640e6b6b59SJacob Faibussowitsch 7650e6b6b59SJacob Faibussowitsch // search the blocks for the owning chunk 7660e6b6b59SJacob Faibussowitsch for (auto &block : pool_) { 7670e6b6b59SJacob Faibussowitsch PetscCall(block.try_find_chunk(old_ptr, &chunk)); 7680e6b6b59SJacob Faibussowitsch if (chunk) break; // found 7690e6b6b59SJacob Faibussowitsch } 7700e6b6b59SJacob Faibussowitsch PetscAssert(chunk, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Memory pool does not own %p, so cannot reallocate it", *ptr); 7710e6b6b59SJacob Faibussowitsch 7720e6b6b59SJacob Faibussowitsch if (chunk->capacity() < new_size) { 7730e6b6b59SJacob Faibussowitsch // chunk does not have enough room, need to grab a fresh chunk and copy to it 7740e6b6b59SJacob Faibussowitsch *ptr = nullptr; 7750e6b6b59SJacob Faibussowitsch PetscCall(chunk->release(stream)); 7760e6b6b59SJacob Faibussowitsch PetscCall(allocate(new_size, ptr, stream)); 7770e6b6b59SJacob Faibussowitsch PetscCall(allocator_.uninitialized_copy(*ptr, old_ptr, new_size, stream)); 7780e6b6b59SJacob Faibussowitsch } else { 7790e6b6b59SJacob Faibussowitsch // chunk had enough room we can simply grow (or shrink) to fit the new size 7800e6b6b59SJacob Faibussowitsch PetscCall(chunk->resize(new_size)); 7810e6b6b59SJacob Faibussowitsch } 7820e6b6b59SJacob Faibussowitsch PetscFunctionReturn(0); 7830e6b6b59SJacob Faibussowitsch } 7840e6b6b59SJacob Faibussowitsch 7850e6b6b59SJacob Faibussowitsch } // namespace memory 7860e6b6b59SJacob Faibussowitsch 7870e6b6b59SJacob Faibussowitsch } // namespace Petsc 7880e6b6b59SJacob Faibussowitsch 7890e6b6b59SJacob Faibussowitsch #endif // PETSC_SEGMENTEDMEMPOOL_HPP 790