-
Notifications
You must be signed in to change notification settings - Fork 788
Draft: [SYCL] Implement resource pool for implementation allocations #5662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
steffenlarsen
wants to merge
17
commits into
intel:sycl
from
steffenlarsen:steffen/rework_redu_res_acq
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
b5ca758
[SYCL] Implement resource pool for implementation allocations
steffenlarsen 7c2f400
Add missing Windows symbols
steffenlarsen 255b422
Clear resource pool when running out of memory
steffenlarsen b671e7b
Fix formatting
steffenlarsen 168cb44
Set platform pointer during first allocation
steffenlarsen 5cd62c9
Merge branch 'sycl' into steffen/rework_redu_res_acq
steffenlarsen 2bd8484
Merge remote-tracking branch 'intel/sycl' into steffen/rework_redu_re…
steffenlarsen e1b2436
Add missing Windows symbol
steffenlarsen cf71eb8
Make base destructor virtual
steffenlarsen 44e2a7c
Fix windows symbols
steffenlarsen 7b60471
Make isEnabled const and align alloc API
steffenlarsen ced4794
Fix formatting
steffenlarsen c139283
Fix creation of available event
steffenlarsen 62aab53
Fix Windows symbol
steffenlarsen 604c15f
Add unittests for resource pool
steffenlarsen 5679ab3
Switch testing to fixed-width integers
steffenlarsen 1732e6e
Merge remote-tracking branch 'intel/sycl' into steffen/rework_redu_re…
steffenlarsen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,268 @@ | ||
//==------------- resource_pool.hpp - USM resource pool ---------*- C++-*---==// | ||
// | ||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
// See https://llvm.org/LICENSE.txt for license information. | ||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#pragma once | ||
|
||
#include <CL/sycl/buffer.hpp> | ||
#include <CL/sycl/detail/defines_elementary.hpp> | ||
|
||
#include <cassert> | ||
#include <memory> | ||
#include <mutex> | ||
#include <set> | ||
|
||
__SYCL_INLINE_NAMESPACE(cl) { | ||
namespace sycl { | ||
namespace detail { | ||
|
||
// Forward declarations | ||
class context_impl; | ||
class queue_impl; | ||
class device_impl; | ||
class platform_impl; | ||
class ResourcePool; | ||
|
||
struct __SYCL_EXPORT ManagedResourceBase { | ||
ManagedResourceBase() = delete; | ||
virtual ~ManagedResourceBase(); | ||
|
||
protected: | ||
ManagedResourceBase(size_t Size, RT::PiMem Mem, ResourcePool *Origin) | ||
: MSize(Size), MMem(Mem), MOrigin(Origin) {} | ||
|
||
/// Size of the memory in the managed resource. | ||
size_t MSize; | ||
|
||
/// Memory associated with the managed resource. | ||
RT::PiMem MMem; | ||
|
||
/// The resource pool the resource was taken from. | ||
ResourcePool *MOrigin; | ||
|
||
friend class ResourcePool; | ||
}; | ||
|
||
template <typename T, int Dims> | ||
struct ManagedResource : public ManagedResourceBase { | ||
ManagedResource() = delete; | ||
|
||
/// Gets the buffer associated with the resource. | ||
/// | ||
/// \return the buffer associated with the resource. | ||
buffer<T, Dims, buffer_allocator, void> &getBuffer() { return MBuffer; } | ||
|
||
private: | ||
/// Creates a buffer implementation. | ||
/// | ||
/// \param Size is the size of the memory passed to the buffer. | ||
/// \param Mem is the memory for the buffer. | ||
/// \param ContextImplPtr is the context implementation the memory is | ||
/// associated with. | ||
/// \param AvailableEvent is an event tied to the availability of the data in | ||
/// the memory. | ||
/// \return a shared pointer to the resulting buffer implementation. | ||
static std::shared_ptr<buffer_impl> | ||
createBufferImpl(size_t Size, RT::PiMem Mem, | ||
const std::shared_ptr<context_impl> &ContextImplPtr, | ||
event AvailableEvent) { | ||
return std::make_shared<buffer_impl>( | ||
Mem, createSyclObjFromImpl<context>(ContextImplPtr), Size, | ||
make_unique_ptr<detail::SYCLMemObjAllocatorHolder<buffer_allocator>>(), | ||
AvailableEvent); | ||
} | ||
|
||
ManagedResource(size_t Size, RT::PiMem Mem, ResourcePool *Origin, | ||
range<Dims> Range, | ||
const std::shared_ptr<context_impl> &ContextImplPtr, | ||
event AvailableEvent = event{}) | ||
: ManagedResourceBase(Size, Mem, Origin), | ||
MBuffer(createBufferImpl(Size, Mem, ContextImplPtr, AvailableEvent), | ||
Range, 0, | ||
/*IsSubBuffer=*/false) {} | ||
|
||
// Constructor for when pool is disabled. | ||
ManagedResource(ResourcePool *Origin, range<Dims> Range, T *DataPtr) | ||
: ManagedResourceBase(0, nullptr, Origin), MBuffer(DataPtr, Range) {} | ||
|
||
/// Buffer owned by the resource. | ||
buffer<T, Dims, buffer_allocator, void> MBuffer; | ||
|
||
friend class ResourcePool; | ||
}; | ||
|
||
class __SYCL_EXPORT ResourcePool { | ||
private: | ||
/// Free entry in the resource pool. This represents an allocation owned by | ||
/// the pool that is not currently in use. | ||
struct FreeEntry { | ||
/// Byte size of the free entry. | ||
size_t Size; | ||
/// Memory allocation of the free entry. | ||
RT::PiMem Mem; | ||
}; | ||
|
||
/// Comparison of free entries by size. This is used for fast lookup by size | ||
/// in the pool. | ||
struct FreeEntryCompare { | ||
using is_transparent = void; | ||
bool operator()(FreeEntry const &lhs, FreeEntry const &rhs) const { | ||
return lhs.Size < rhs.Size; | ||
} | ||
bool operator()(FreeEntry const &lhs, size_t rhs) const { | ||
return lhs.Size < rhs; | ||
} | ||
bool operator()(size_t lhs, FreeEntry const &rhs) const { | ||
return lhs < rhs.Size; | ||
} | ||
}; | ||
|
||
/// Extracts a free entry from the pool that fits the size required. If there | ||
/// is no suitable entry, new memory will be allocated. | ||
/// | ||
/// \param Range is the range of the resulting buffer. | ||
/// \param QueueImplPtr is the queue with the context to allocate memory in. | ||
/// \param DataPtr is the pointer to data on the host to initialize the | ||
/// associated memory with. This will only be used if a new entry is | ||
/// allocated. | ||
/// \param IsNewEntry will be set to true if the entry was newly allocated in | ||
/// the pool and false if it was found in the existing free entries in | ||
/// the pool. This is not set if it is nullptr. | ||
/// \return a shared pointer to the new managed resource. | ||
FreeEntry getOrAllocateEntry(const size_t Size, | ||
const std::shared_ptr<queue_impl> &QueueImplPtr, | ||
void *DataPtr = nullptr, | ||
bool *IsNewEntry = nullptr); | ||
|
||
/// Extracts a free entry from the pool that fits the size required. If there | ||
/// is no suitable entry, new memory will be allocated. The memory will be | ||
/// initialized with the data given. | ||
/// | ||
/// \param Size is the size of the free entry to find or allocate. | ||
/// \param QueueImplPtr is the queue with the context to allocate memory in. | ||
/// \param DataPtr is the pointer to data on the host to initialize the | ||
/// associated memory with. | ||
/// \param AvailableEvent will be set to an event that is tied to the | ||
/// initialization of the memory. | ||
/// \param IsNewEntry will be set to true if the entry was newly allocated in | ||
/// the pool and false if it was found in the existing free entries in | ||
/// the pool. This is not set if it is nullptr. | ||
/// \return a shared pointer to the new managed resource. | ||
FreeEntry getOrAllocateEntry(const size_t Size, | ||
const std::shared_ptr<queue_impl> &QueueImplPtr, | ||
void *DataPtr, event *AvailableEvent, | ||
bool *IsNewEntry = nullptr); | ||
|
||
/// Gets the context implementation associtated with a queue implementation. | ||
/// | ||
/// \param QueueImplPtr is the queue implementation to get the context | ||
/// implementation from. \return the context implementation from the queue | ||
/// implementation. | ||
static const std::shared_ptr<context_impl> & | ||
getQueueContextImpl(const std::shared_ptr<queue_impl> &QueueImplPtr); | ||
|
||
using ContextPtr = context_impl *; | ||
|
||
public: | ||
/// Removes and deallocates all free entries currently in the pool. | ||
void clear(); | ||
|
||
ResourcePool(); | ||
ResourcePool(const ResourcePool &) = delete; | ||
~ResourcePool() { | ||
clear(); | ||
assert(MAllocCount == 0 && "Not all resources have returned to the pool."); | ||
} | ||
|
||
/// Returns true if the resource pool is enabled and false otherwise. | ||
/// | ||
/// \return a boolean value specifying whether the pool is enabled. | ||
bool isEnabled() const { return MIsPoolingEnabled; } | ||
|
||
/// Creates a managed resource from the pool. | ||
/// | ||
/// \param Range is the range of the resulting buffer. | ||
/// \param QueueImplPtr is the queue with the context to allocate memory in. | ||
/// \return a shared pointer to the new managed resource. | ||
template <typename T, int Dims> | ||
std::shared_ptr<ManagedResource<T, Dims>> | ||
getOrAllocateResource(range<Dims> Range, | ||
const std::shared_ptr<queue_impl> &QueueImplPtr) { | ||
// If pool is disabled we return a buffer that will not return to the pool. | ||
if (!MIsPoolingEnabled) | ||
return std::shared_ptr<ManagedResource<T, Dims>>{ | ||
new ManagedResource<T, Dims>(this, Range, nullptr)}; | ||
|
||
// Get or allocate a free entry that fits the requirements. | ||
FreeEntry Entry = | ||
getOrAllocateEntry(Range.size() * sizeof(T), QueueImplPtr); | ||
return std::shared_ptr<ManagedResource<T, Dims>>{ | ||
new ManagedResource<T, Dims>(Entry.Size, Entry.Mem, this, Range, | ||
getQueueContextImpl(QueueImplPtr))}; | ||
} | ||
|
||
/// Creates a managed resource from the pool and sets te data of the | ||
/// associated memory. | ||
/// | ||
/// \param Range is the range of the resulting buffer. | ||
/// \param QueueImplPtr is the queue with the context to allocate memory in. | ||
/// \param DataPtr is the pointer to data on the host to initialize the | ||
/// resource with. This must contain at least the size of Range. | ||
/// \return a shared pointer to the new managed resource. | ||
template <typename T, int Dims> | ||
std::shared_ptr<ManagedResource<T, Dims>> | ||
getOrAllocateResource(range<Dims> Range, | ||
const std::shared_ptr<queue_impl> &QueueImplPtr, | ||
T *DataPtr) { | ||
// If pool is disabled we return a buffer that will not return to the pool. | ||
if (!MIsPoolingEnabled) | ||
return std::shared_ptr<ManagedResource<T, Dims>>{ | ||
new ManagedResource<T, Dims>(this, Range, DataPtr)}; | ||
|
||
// Get or allocate a free entry that fits the requirements. | ||
event AvailableEvent; | ||
FreeEntry Entry = getOrAllocateEntry(Range.size() * sizeof(T), QueueImplPtr, | ||
DataPtr, &AvailableEvent); | ||
return std::shared_ptr<ManagedResource<T, Dims>>{ | ||
new ManagedResource<T, Dims>(Entry.Size, Entry.Mem, this, Range, | ||
getQueueContextImpl(QueueImplPtr), | ||
AvailableEvent)}; | ||
} | ||
|
||
private: | ||
/// Returns a resouce to the pool. | ||
/// | ||
/// \param Size is the size of the resource. | ||
/// \param Mem is the memory of the resource. | ||
void returnResourceToPool(const size_t Size, RT::PiMem Mem) { | ||
std::lock_guard<std::mutex> Lock{MMutex}; | ||
MFreeEntries.insert({Size, Mem}); | ||
} | ||
|
||
friend struct ManagedResourceBase; | ||
|
||
/// Is true if the pool is enabled and false otherwise. This is controlled by | ||
/// the SYCL_DISABLE_AUXILIARY_RESOURCE_POOL config. | ||
const bool MIsPoolingEnabled; | ||
|
||
/// The platform associated with the pool. | ||
std::shared_ptr<platform_impl> MPlatform; | ||
|
||
/// Counter for allocations done by the pool that are currently alive. This | ||
/// includes managed resources that are currently alive. | ||
size_t MAllocCount = 0; | ||
|
||
/// A set of all free entries in the pool. | ||
std::multiset<FreeEntry, FreeEntryCompare> MFreeEntries; | ||
|
||
/// Mutex protecting access to the pool. | ||
std::mutex MMutex; | ||
}; | ||
|
||
} // namespace detail | ||
} // namespace sycl | ||
} // __SYCL_INLINE_NAMESPACE(cl) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this c-tor supersede the other one? That is, should the other c-tor be marked with TODO for removal when ABI break is allowed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe so. The other constructor is intended to be user-facing and as such has additional checks for argument validity. The new constructor is meant for internal use so we do not need the additional checks.