alpaka
Abstraction Library for Parallel Kernel Acceleration
Loading...
Searching...
No Matches
QueueGenericSyclBase.hpp
Go to the documentation of this file.
1/* Copyright 2024 Jan Stephan, Antonio Di Pilato, Luca Ferragina, Andrea Bocci, Aurora Perego
2 * SPDX-License-Identifier: MPL-2.0
3 */
4
5#pragma once
6
7#include "alpaka/acc/Tag.hpp"
13
14#include <algorithm>
15#include <exception>
16#include <memory>
17#include <mutex>
18#include <shared_mutex>
19#include <type_traits>
20#include <utility>
21#include <vector>
22
23#ifdef ALPAKA_ACC_SYCL_ENABLED
24
25# include <sycl/sycl.hpp>
26
27namespace alpaka
28{
29 template<concepts::Tag TTag>
30 class DevGenericSycl;
31
32 template<concepts::Tag TTag>
33 class EventGenericSycl;
34
35 namespace detail
36 {
37 template<typename T, typename = void>
38 inline constexpr auto is_sycl_task = false;
39
40 template<typename T>
41 inline constexpr auto is_sycl_task<T, std::void_t<decltype(T::is_sycl_task)>> = true;
42
43 template<typename T, typename = void>
44 inline constexpr auto is_sycl_kernel = false;
45
46 template<typename T>
47 inline constexpr auto is_sycl_kernel<T, std::void_t<decltype(T::is_sycl_kernel)>> = true;
48
49 class QueueGenericSyclImpl
50 {
51 public:
52 QueueGenericSyclImpl(sycl::context context, sycl::device device)
53 : m_queue{
54 std::move(context), // This is important. In SYCL a device can belong to multiple contexts.
55 std::move(device),
56 {sycl::property::queue::enable_profiling{}, sycl::property::queue::in_order{}}}
57 {
58 }
59
60 // This class will only exist as a pointer. We don't care about copy and move semantics.
61 QueueGenericSyclImpl(QueueGenericSyclImpl const& other) = delete;
62 auto operator=(QueueGenericSyclImpl const& rhs) -> QueueGenericSyclImpl& = delete;
63
64 QueueGenericSyclImpl(QueueGenericSyclImpl&& other) noexcept = delete;
65 auto operator=(QueueGenericSyclImpl&& rhs) noexcept -> QueueGenericSyclImpl& = delete;
66
67 ~QueueGenericSyclImpl()
68 {
69 try
70 {
71 m_queue.wait_and_throw();
72 }
73 catch(sycl::exception const& err)
74 {
75 std::cerr << "Caught SYCL exception while destructing a SYCL queue: " << err.what() << " ("
76 << err.code() << ')' << std::endl;
77 }
78 catch(std::exception const& err)
79 {
80 std::cerr << "The following runtime error(s) occured while destructing a SYCL queue:" << err.what()
81 << std::endl;
82 }
83 }
84
85 // Don't call this without locking first!
86 auto clean_dependencies() -> void
87 {
88 // Clean up completed events
89 auto const start = std::begin(m_dependencies);
90 auto const old_end = std::end(m_dependencies);
91 auto const new_end = std::remove_if(
92 start,
93 old_end,
94 [](sycl::event ev) {
95 return ev.get_info<sycl::info::event::command_execution_status>()
96 == sycl::info::event_command_status::complete;
97 });
98
99 m_dependencies.erase(new_end, old_end);
100 }
101
102 auto register_dependency(sycl::event event) -> void
103 {
104 std::lock_guard<std::shared_mutex> lock{m_mutex};
105
106 clean_dependencies();
107 m_dependencies.push_back(event);
108 }
109
110 auto empty() const -> bool
111 {
112 std::shared_lock<std::shared_mutex> lock{m_mutex};
113 return m_last_event.get_info<sycl::info::event::command_execution_status>()
114 == sycl::info::event_command_status::complete;
115 }
116
117 auto wait() -> void
118 {
119 // SYCL queues are thread-safe.
120 m_queue.wait_and_throw();
121 }
122
123 auto get_last_event() const -> sycl::event
124 {
125 std::shared_lock<std::shared_mutex> lock{m_mutex};
126 return m_last_event;
127 }
128
129 // Perfect forwarding of the task is a backend-specific workaround for oneAPI SYCL internally converting
130 // host tasks to std::function&&. The portable way is to pass the task by const reference, as done by
131 // the other backends.
132 template<bool TBlocking, typename TTask>
133 auto enqueue(TTask&& task) -> void
134 {
135 {
136 using TaskType = std::decay_t<TTask>;
137 std::lock_guard<std::shared_mutex> lock{m_mutex};
138
139 clean_dependencies();
140
141 // Execute task
142 if constexpr(is_sycl_task<TaskType> && !is_sycl_kernel<TaskType>) // Copy / Fill
143 {
144 m_last_event = task(m_queue, m_dependencies); // Will call queue.{copy, fill} internally
145 }
146 else
147 {
148 m_last_event = m_queue.submit(
149 [this, captured_task = std::forward<TTask>(task)](sycl::handler& cgh) mutable
150 {
151 if(!m_dependencies.empty())
152 cgh.depends_on(m_dependencies);
153
154 if constexpr(is_sycl_kernel<TaskType>) // Kernel
155 captured_task(cgh); // Will call cgh.parallel_for internally
156 else // Host
157 cgh.host_task(std::move(captured_task));
158 });
159 }
160
161 m_dependencies.clear();
162 }
163
164 if constexpr(TBlocking)
165 wait();
166 }
167
168 [[nodiscard]] auto getNativeHandle() const noexcept
169 {
170 return m_queue;
171 }
172
173 std::vector<sycl::event> m_dependencies;
174 sycl::event m_last_event;
175 std::shared_mutex mutable m_mutex;
176
177 private:
178 sycl::queue m_queue;
179 };
180
181 template<concepts::Tag TTag, bool TBlocking>
182 class QueueGenericSyclBase
183 : public interface::Implements<ConceptCurrentThreadWaitFor, QueueGenericSyclBase<TTag, TBlocking>>
184 , public interface::Implements<ConceptQueue, QueueGenericSyclBase<TTag, TBlocking>>
185 , public interface::Implements<ConceptGetDev, QueueGenericSyclBase<TTag, TBlocking>>
186 {
187 public:
188 QueueGenericSyclBase(DevGenericSycl<TTag> const& dev)
189 : m_dev{dev}
190 , m_spQueueImpl{std::make_shared<detail::QueueGenericSyclImpl>(
191 dev.getNativeHandle().second,
192 dev.getNativeHandle().first)}
193 {
194 m_dev.m_impl->register_queue(m_spQueueImpl);
195 }
196
197 friend auto operator==(QueueGenericSyclBase const& lhs, QueueGenericSyclBase const& rhs) -> bool
198 {
199 return (lhs.m_dev == rhs.m_dev) && (lhs.m_spQueueImpl == rhs.m_spQueueImpl);
200 }
201
202 friend auto operator!=(QueueGenericSyclBase const& lhs, QueueGenericSyclBase const& rhs) -> bool
203 {
204 return !(lhs == rhs);
205 }
206
207 [[nodiscard]] auto getNativeHandle() const noexcept
208 {
209 return m_spQueueImpl->getNativeHandle();
210 }
211
212 DevGenericSycl<TTag> m_dev;
213 std::shared_ptr<detail::QueueGenericSyclImpl> m_spQueueImpl;
214 };
215 } // namespace detail
216
217 namespace trait
218 {
219 //! The SYCL blocking queue device type trait specialization.
220 template<concepts::Tag TTag, bool TBlocking>
221 struct DevType<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
222 {
223 using type = DevGenericSycl<TTag>;
224 };
225
226 //! The SYCL blocking queue device get trait specialization.
227 template<concepts::Tag TTag, bool TBlocking>
228 struct GetDev<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
229 {
230 static auto getDev(alpaka::detail::QueueGenericSyclBase<TTag, TBlocking> const& queue)
231 {
233 return queue.m_dev;
234 }
235 };
236
237 //! The SYCL blocking queue event type trait specialization.
238 template<concepts::Tag TTag, bool TBlocking>
239 struct EventType<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
240 {
241 using type = EventGenericSycl<TTag>;
242 };
243
244 //! The SYCL blocking queue enqueue trait specialization.
245 template<concepts::Tag TTag, bool TBlocking, typename TTask>
246 struct Enqueue<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>, TTask>
247 {
248 template<typename UTask>
249 static auto enqueue(alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>& queue, UTask&& task) -> void
250 {
252 queue.m_spQueueImpl->template enqueue<TBlocking>(std::forward<UTask>(task));
253 }
254 };
255
256 //! The SYCL blocking queue test trait specialization.
257 template<concepts::Tag TTag, bool TBlocking>
258 struct Empty<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
259 {
260 static auto empty(alpaka::detail::QueueGenericSyclBase<TTag, TBlocking> const& queue) -> bool
261 {
263 return queue.m_spQueueImpl->empty();
264 }
265 };
266
267 //! The SYCL blocking queue thread wait trait specialization.
268 //!
269 //! Blocks execution of the calling thread until the queue has finished processing all previously requested
270 //! tasks (kernels, data copies, ...)
271 template<concepts::Tag TTag, bool TBlocking>
272 struct CurrentThreadWaitFor<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
273 {
274 static auto currentThreadWaitFor(alpaka::detail::QueueGenericSyclBase<TTag, TBlocking> const& queue)
275 -> void
276 {
278 queue.m_spQueueImpl->wait();
279 }
280 };
281
282 //! The SYCL queue native handle trait specialization.
283 template<concepts::Tag TTag, bool TBlocking>
284 struct NativeHandle<alpaka::detail::QueueGenericSyclBase<TTag, TBlocking>>
285 {
286 [[nodiscard]] static auto getNativeHandle(
287 alpaka::detail::QueueGenericSyclBase<TTag, TBlocking> const& queue)
288 {
289 return queue.getNativeHandle();
290 }
291 };
292 } // namespace trait
293} // namespace alpaka
294#endif
#define ALPAKA_DEBUG_MINIMAL_LOG_SCOPE
Definition Debug.hpp:55
constexpr ALPAKA_FN_HOST_ACC bool operator==(Complex< T > const &lhs, Complex< T > const &rhs)
Equality of two complex numbers.
Definition Complex.hpp:294
constexpr ALPAKA_FN_HOST_ACC bool operator!=(Complex< T > const &lhs, Complex< T > const &rhs)
Inequality of two complex numbers.
Definition Complex.hpp:320
The alpaka accelerator library.
ALPAKA_FN_HOST auto getDev(T const &t)
Definition Traits.hpp:75
ALPAKA_FN_HOST auto getNativeHandle(TImpl const &impl)
Get the native handle of the alpaka object. It will return the alpaka object handle if there is any,...
Definition Traits.hpp:29
ALPAKA_FN_HOST auto empty(TQueue const &queue) -> bool
Tests if the queue is empty (all ops in the given queue have been completed).
Definition Traits.hpp:65
ALPAKA_FN_HOST auto enqueue(TQueue &queue, TTask &&task) -> void
Queues the given task in the given queue.
Definition Traits.hpp:54
ALPAKA_FN_HOST auto wait(TAwaited const &awaited) -> void
Waits the thread for the completion of the given awaited action to complete.
Definition Traits.hpp:34
decltype(getNativeHandle(std::declval< TImpl >())) NativeHandle
Alias to the type of the native handle.
Definition Traits.hpp:36
STL namespace.