Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
resource_pool.hpp
Go to the documentation of this file.
1#pragma once
2
10
11#include <condition_variable>
12#include <cstddef>
13#include <mutex>
14#include <optional>
15#include <utility>
16#include <vector>
17
18namespace uni20::cuda
19{
20
21template <class Resource> class ResourcePool;
22template <class Resource> class ResourceLease;
23
24namespace detail
25{
26
28template <class Resource> class ResourceWaiter {
29 public:
30 using notify_type = void (*)(ResourceWaiter&, ResourceLease<Resource>) noexcept;
31
32 explicit ResourceWaiter(notify_type notify) noexcept : notify_(notify) { CHECK(notify_ != nullptr); }
33 ResourceWaiter(ResourceWaiter const&) = delete;
34 ResourceWaiter& operator=(ResourceWaiter const&) = delete;
35
36 private:
37 void notify(ResourceLease<Resource> lease) noexcept { notify_(*this, std::move(lease)); }
38
39 notify_type notify_;
40 ResourceWaiter* next_ = nullptr;
41 bool queued_ = false;
42
43 friend class ResourcePool<Resource>;
44};
45
46} // namespace detail
47
51template <class Resource> class ResourceLease {
52 public:
53 ResourceLease() noexcept = default;
54 ResourceLease(ResourceLease const&) = delete;
55 ResourceLease& operator=(ResourceLease const&) = delete;
56
57 ResourceLease(ResourceLease&& other) noexcept
58 : pool_(std::exchange(other.pool_, nullptr)), slot_(std::exchange(other.slot_, 0))
59 {}
60
62 {
63 if (this != &other)
64 {
65 this->release();
66 pool_ = std::exchange(other.pool_, nullptr);
67 slot_ = std::exchange(other.slot_, 0);
68 }
69 return *this;
70 }
71
72 ~ResourceLease() { this->release(); }
73
75 [[nodiscard]] explicit operator bool() const noexcept { return pool_ != nullptr; }
76
78 [[nodiscard]] Resource& get() const;
79
81 void release() noexcept;
82
83 private:
84 ResourceLease(ResourcePool<Resource>& pool, std::size_t slot) noexcept : pool_(&pool), slot_(slot) {}
85
86 ResourcePool<Resource>* pool_ = nullptr;
87 std::size_t slot_ = 0;
88
89 friend class ResourcePool<Resource>;
90};
91
96template <class Resource> class ResourcePool {
97 public:
98 using resource_type = Resource;
100 using waiter_type = detail::ResourceWaiter<Resource>;
101
103 explicit ResourcePool(std::vector<Resource> resources)
104 : resources_(std::move(resources)), idle_(resources_.size(), true), idle_count_(resources_.size())
105 {
106 CHECK(!resources_.empty(), "resource pool requires at least one slot");
107 }
108
109 ResourcePool(ResourcePool const&) = delete;
113
115 {
116 std::lock_guard lock(mutex_);
117 CHECK(waiter_head_ == nullptr, "destroying resource pool with queued waiters");
118 CHECK(idle_count_ == resources_.size(), "destroying resource pool with active leases", idle_count_,
119 resources_.size());
120 }
121
123 [[nodiscard]] std::optional<lease_type> try_acquire()
124 {
125 std::lock_guard lock(mutex_);
126 auto const slot = this->take_idle_slot_locked();
127 if (!slot) return std::nullopt;
128 return lease_type(*this, *slot);
129 }
130
132 [[nodiscard]] lease_type acquire()
133 {
134 std::unique_lock lock(mutex_);
135 available_.wait(lock, [this] { return idle_count_ != 0; });
136 auto const slot = this->take_idle_slot_locked();
137 CHECK(slot.has_value());
138 return lease_type(*this, *slot);
139 }
140
142 [[nodiscard]] std::size_t size() const noexcept { return resources_.size(); }
143
145 [[nodiscard]] std::size_t idle_count() const noexcept
146 {
147 std::lock_guard lock(mutex_);
148 return idle_count_;
149 }
150
152 [[nodiscard]] std::optional<lease_type> acquire_or_enqueue(waiter_type& waiter) noexcept
153 {
154 std::lock_guard lock(mutex_);
155 CHECK(!waiter.queued_);
156 if (auto const slot = this->take_idle_slot_locked())
157 {
158 return lease_type(*this, *slot);
159 }
160
161 waiter.queued_ = true;
162 waiter.next_ = nullptr;
163 if (waiter_tail_ == nullptr)
164 {
165 waiter_head_ = &waiter;
166 }
167 else
168 {
169 waiter_tail_->next_ = &waiter;
170 }
171 waiter_tail_ = &waiter;
172 return std::nullopt;
173 }
174
175 private:
176 [[nodiscard]] std::optional<std::size_t> take_idle_slot_locked() noexcept
177 {
178 if (idle_count_ == 0) return std::nullopt;
179 for (std::size_t slot = 0; slot < idle_.size(); ++slot)
180 {
181 if (idle_[slot])
182 {
183 idle_[slot] = false;
184 --idle_count_;
185 return slot;
186 }
187 }
188 PANIC("resource pool idle count is inconsistent", idle_count_, idle_.size());
189 }
190
191 [[nodiscard]] Resource& resource(std::size_t slot)
192 {
193 CHECK(slot < resources_.size(), slot, resources_.size());
194 return resources_[slot];
195 }
196
197 void release(std::size_t slot) noexcept
198 {
199 waiter_type* waiter = nullptr;
200 {
201 std::lock_guard lock(mutex_);
202 CHECK(slot < resources_.size(), slot, resources_.size());
203 CHECK(!idle_[slot], "resource pool slot was released twice", slot);
204
205 if (waiter_head_ == nullptr)
206 {
207 idle_[slot] = true;
208 ++idle_count_;
209 available_.notify_one();
210 return;
211 }
212
213 waiter = waiter_head_;
214 waiter_head_ = waiter->next_;
215 if (waiter_head_ == nullptr) waiter_tail_ = nullptr;
216 waiter->next_ = nullptr;
217 waiter->queued_ = false;
218 }
219
220 waiter->notify(lease_type(*this, slot));
221 }
222
223 mutable std::mutex mutex_;
224 std::condition_variable available_;
225 std::vector<Resource> resources_;
226 std::vector<bool> idle_;
227 std::size_t idle_count_;
228 waiter_type* waiter_head_ = nullptr;
229 waiter_type* waiter_tail_ = nullptr;
230
231 friend class ResourceLease<Resource>;
232};
233
234template <class Resource> Resource& ResourceLease<Resource>::get() const
235{
236 CHECK(pool_ != nullptr, "cannot access an empty resource lease");
237 return pool_->resource(slot_);
238}
239
240template <class Resource> void ResourceLease<Resource>::release() noexcept
241{
242 if (pool_ == nullptr) return;
243 auto* pool = std::exchange(pool_, nullptr);
244 auto const slot = std::exchange(slot_, 0);
245 pool->release(slot);
246}
247
248} // namespace uni20::cuda
Move-only exclusive lease of one provider-resource pool slot.
Definition resource_pool.hpp:51
ResourceLease & operator=(ResourceLease &&other) noexcept
Definition resource_pool.hpp:61
void release() noexcept
Return the resource to its pool before this object's destruction.
Definition resource_pool.hpp:240
Resource & get() const
Access the exclusively leased provider resource.
Definition resource_pool.hpp:234
~ResourceLease()
Definition resource_pool.hpp:72
ResourceLease() noexcept=default
Fixed-capacity pool of preconstructed provider resources.
Definition resource_pool.hpp:96
ResourcePool & operator=(ResourcePool &&)=delete
std::optional< lease_type > try_acquire()
Try to acquire one idle resource without waiting.
Definition resource_pool.hpp:123
ResourceLease< Resource > lease_type
Definition resource_pool.hpp:99
~ResourcePool()
Definition resource_pool.hpp:114
ResourcePool(ResourcePool const &)=delete
ResourcePool(std::vector< Resource > resources)
Take ownership of the resources that form this pool's slots.
Definition resource_pool.hpp:103
ResourcePool & operator=(ResourcePool const &)=delete
Resource resource_type
Definition resource_pool.hpp:98
detail::ResourceWaiter< Resource > waiter_type
Definition resource_pool.hpp:100
ResourcePool(ResourcePool &&)=delete
std::optional< lease_type > acquire_or_enqueue(waiter_type &waiter) noexcept
Register an internal awaiter unless a resource is immediately available.
Definition resource_pool.hpp:152
std::size_t idle_count() const noexcept
Return the number of resources currently available for acquisition.
Definition resource_pool.hpp:145
lease_type acquire()
Block until one resource can be leased.
Definition resource_pool.hpp:132
std::size_t size() const noexcept
Return the fixed number of resource slots.
Definition resource_pool.hpp:142
Definition qnum.hpp:403
Definition cublas/execution.hpp:18
#define CHECK(cond,...)
Definition trace.hpp:299
#define PANIC(...)
Definition trace.hpp:382