9.2
general documentation
cs_dispatch_queue.h
Go to the documentation of this file.
1#pragma once
2
3/*============================================================================
4 * Class to dispatch computation using various runtimes (OpenMP, CUDA, ...)
5 * with explicit dependencies between launched kernels (inspired by SYCL).
6 *============================================================================*/
7
8/*
9 This file is part of code_saturne, a general-purpose CFD tool.
10
11 Copyright (C) 1998-2026 EDF S.A.
12
13 This program is free software; you can redistribute it and/or modify it under
14 the terms of the GNU General Public License as published by the Free Software
15 Foundation; either version 2 of the License, or (at your option) any later
16 version.
17
18 This program is distributed in the hope that it will be useful, but WITHOUT
19 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
21 details.
22
23 You should have received a copy of the GNU General Public License along with
24 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
25 Street, Fifth Floor, Boston, MA 02110-1301, USA.
26*/
27
28/*----------------------------------------------------------------------------*/
29
30#include "base/cs_defs.h"
31
32/*----------------------------------------------------------------------------
33 * Standard C++ library headers
34 *----------------------------------------------------------------------------*/
35
36#include <chrono>
37#include <initializer_list>
38#include <tuple>
39#include <type_traits>
40#include <utility>
41
42#if defined(__CUDACC__)
43#include <cuda.h>
44#include <cuda_runtime.h>
45#elif defined(__HIPCC__)
46#include <hip/hip_runtime.h>
47#include <hip/hip_runtime.h>
48#else
49#endif
50
51/*----------------------------------------------------------------------------
52 * Local headers
53 *----------------------------------------------------------------------------*/
54
55#include "base/cs_assert.h"
56#include "base/cs_mem.h"
57
58#include "base/cs_dispatch.h"
59
60/*=============================================================================
61 * Macro definitions
62 *============================================================================*/
63
65#ifndef CS_DISPATCH_QUEUE_FORCE_SYNC
66#define CS_DISPATCH_QUEUE_FORCE_SYNC 0
67#endif
68
69/*============================================================================
70 * Type definitions
71 *============================================================================*/
72
73struct cs_event;
74struct cs_task;
75
76/*----------------------------------------------------------------------------*/
80/*----------------------------------------------------------------------------*/
81
82struct cs_event {
84#if defined(__CUDACC__)
85 cudaEvent_t;
86#elif defined(__HIPCC__)
87 hipEvent_t;
88#else
89 std::chrono::steady_clock::time_point;
90#endif
91
93
95
97 {
98#if defined(__CUDACC__)
99 cudaEventCreate(&event_impl);
100#elif defined(__HIPCC__)
101 hipEventCreate(&event_impl);
102#endif
103 }
104
106
107 cs_event(cs_event const &other) = delete;
108 cs_event &
109 operator=(cs_event const &) = delete;
110
112#if defined(__CUDACC__) || defined(__HIPCC__)
113 {
114 event_impl = other.event_impl;
115 other.event_impl = nullptr;
116 }
117#else
118 = default;
119#endif
120
121 cs_event &
123#if defined(__CUDACC__) || defined(__HIPCC__)
124 {
125 if (event_impl != nullptr) {
126#if defined(__CUDACC__)
127 cudaEventDestroy(event_impl);
128#elif defined(__HIPCC__)
129 hipEventDestroy(event_impl);
130#endif
131 }
132
133 event_impl = other.event_impl;
134 other.event_impl = nullptr;
135
136 return *this;
137 }
138#else
139 = default;
140#endif
141
145 {
146 return event_impl;
147 }
148
150#if defined(__CUDACC__)
151 {
152 if (event_impl != nullptr) {
153 cudaEventDestroy(event_impl);
154 }
155 }
156#elif defined(__HIPCC__)
157 {
158 if (event_impl != nullptr) {
159 hipEventDestroy(event_impl);
160 }
161 }
162#else
163 = default;
164#endif
165
166 // Actions
167
169
170 void
172 {
173#if defined(__CUDACC__)
174 cudaEventSynchronize(event_impl);
175#elif defined(__HIPCC__)
176 hipEventSynchronize(event_impl);
177#endif
178 }
179};
180
181/*----------------------------------------------------------------------------*/
185/*----------------------------------------------------------------------------*/
186
188 cs_event *event_ptr;
189
190public:
192
193 cs_event_ref(cs_event &event) : event_ptr(&event) {}
194
195 cs_event_ref() = delete;
196
197 cs_event_ref(cs_event_ref &&other) = default;
198 cs_event_ref(cs_event_ref const &other) = default;
200 operator=(cs_event_ref &&) & = default;
202 operator=(cs_event_ref const &) & = default;
203
205 cs_event *
207 {
208 return event_ptr;
209 }
210
212 cs_event &
214 {
215 return *event_ptr;
216 }
217
222 {
223 return ~(*event_ptr);
224 }
225};
226
227/*----------------------------------------------------------------------------*/
235/*----------------------------------------------------------------------------*/
236
237class cs_task {
238 cs_dispatch_context context_;
239
241 cs_event start_event;
242
244 cs_event end_event;
245
246public:
247 cs_task(cs_task const &) = delete;
248 cs_task &
249 operator=(cs_task const &) = delete;
250
251 cs_task(cs_task &&) = default;
252 cs_task &
253 operator=(cs_task &&) = default;
254
256 cs_task(cs_dispatch_context context = {}) : context_(std::move(context))
257 {
258#if defined(__CUDACC__)
259 cudaStream_t new_stream;
260 cudaStreamCreate(&new_stream);
261 context_.set_stream(new_stream);
262 cudaEventRecord(~start_event, context_.stream());
263#elif defined(__HIPCC__)
264 hipStream_t new_stream;
265 hipStreamCreate(&new_stream);
266 context_.set_stream(new_stream);
267 hipEventRecord(~start_event, context_.stream());
268#else
269 ~start_event = std::chrono::steady_clock::now();
270#endif
271 }
272
274 void
276 {
277#if defined(__CUDACC__)
278 cudaStreamWaitEvent(context_.stream(), ~event);
279#elif defined(__HIPCC__)
280 hipStreamWaitEvent(context_.stream(), ~event);
281#endif
282 }
283
286 void
287 add_dependency(std::initializer_list<cs_event_ref> const &sync_events)
288 {
289#if defined(__CUDACC__) || defined(__HIPCC__)
290 for (auto const &event : sync_events) {
291 add_dependency(event);
292 }
293#endif
294 }
295
297 void
299 {
300 end_event.wait();
301 }
302
306 {
307#if defined(__CUDACC__)
308 cudaEventRecord(~end_event, context_.stream());
309#elif defined(__HIPCC__)
310 hipEventRecord(~end_event, context_.stream());
311#else
312 ~end_event = std::chrono::steady_clock::now();
313#endif
314 return { end_event };
315 }
316
318 operator cs_event_ref() { return end_event; }
319
323 {
324 return context_;
325 }
326
330 {
331 return start_event;
332 }
333
337 {
338 return start_event;
339 }
340
343 {
344 context_.wait();
345#if defined(__CUDACC__)
346 cudaStreamDestroy(context_.stream());
347#elif defined(__HIPCC__)
348 hipStreamDestroy(context_.stream());
349#endif
350 }
351};
352
354template <class FunctionType, class... Args>
355class cs_host_task : public cs_task {
356public:
357 cs_host_task(cs_host_task const &) = delete;
359 operator=(cs_host_task const &) = delete;
360
363 operator=(cs_host_task &&) = default;
364
366 using args_tuple_t = std::tuple<Args...>;
367
371#if defined(__CUDACC__) || defined(__HIPCC__)
372 std::tuple<FunctionType, args_tuple_t>;
373#else
374 std::tuple<FunctionType>;
375#endif
376
377private:
380 data_tuple_t data_tuple_;
381
382public:
385 cs_host_task(FunctionType &&function, cs_dispatch_context context)
386 : cs_task(std::move(context)),
387
388#if defined(__CUDACC__) || defined(__HIPCC__)
389 data_tuple_(std::move(function), args_tuple_t{})
390#else
391 data_tuple_(std::move(function))
392#endif
393 {
394 }
395
398#if defined(__CUDACC__)
399 cudaError_t
400#elif defined(__HIPCC__)
401 hipError_t
402#else
403 void
404#endif
405 launch(Args... args)
406 {
407#if defined(__CUDACC__)
408 if (this->get_context().use_gpu()) {
409 // Setting the arguments
410 std::get<1>(data_tuple_) = args_tuple_t{ std::move(args)... };
411
412 // Async launch on the task's own stream
413 return cudaLaunchHostFunc
414 (get_context().stream(),
415 // Wrapper lambda: unwraps the parameter passed as a void* pointer
416 // to invoke the host function
417 [](void *data_tuple_ptr) -> void {
418 auto &[f, args_tuple] = *(data_tuple_t *)(data_tuple_ptr);
419 std::apply(f, args_tuple);
420 },
421 &data_tuple_);
422 }
423 else {
424 this->record_end_event();
425 this->wait();
426 std::get<0>(data_tuple_)(args...);
427 return cudaSuccess;
428 }
429#elif defined(__HIPCC__)
430 if (this->get_context().use_gpu()) {
431 // Setting the arguments
432 std::get<1>(data_tuple_) = args_tuple_t{ std::move(args)... };
433
434 // Async launch on the task's own stream
435 return hipLaunchHostFunc
436 (get_context().stream(),
437 // Wrapper lambda: unwraps the parameter passed as a void* pointer
438 // to invoke the host function
439 [](void *data_tuple_ptr) -> void {
440 auto &[f, args_tuple] = *(data_tuple_t *)(data_tuple_ptr);
441 std::apply(f, args_tuple);
442 },
443 &data_tuple_);
444 }
445 else {
446 this->record_end_event();
447 this->wait();
448 std::get<0>(data_tuple_)(args...);
449 return hipSuccess;
450 }
451#else
452 std::get<0>(data_tuple_)(args...);
453#endif
454 }
455
458 {
459 // We must wait host task termination to avoid data_tuple_
460 // to be unstacked before the task is executed
461 wait();
462 }
463};
464
465/*----------------------------------------------------------------------------*/
470/*----------------------------------------------------------------------------*/
471
473
474public:
477
478 template <class F, class... Args>
479 cs_task
480 parallel_for(cs_lnum_t n, F &&f, Args &&...args)
481 {
483 new_task.get_context().parallel_for(n,
484 std::forward<F>(f),
485 std::forward<Args>(args)...);
486 new_task.record_end_event();
487 return new_task;
488 }
489
490 template <class F, class... Args>
491 cs_task
493 std::initializer_list<cs_event_ref> const &sync_events,
494 F &&f,
495 Args &&...args)
496 {
498 new_task.add_dependency(sync_events);
499 new_task.get_context().parallel_for(n,
500 std::forward<F>(f),
501 std::forward<Args>(args)...);
502 new_task.record_end_event();
503 return new_task;
504 }
505
506 template <class M, class F, class... Args>
507 cs_task
508 parallel_for_i_faces(const M *m, F &&f, Args &&...args)
509 {
512 std::forward<F>(f),
513 std::forward<Args>(args)...);
514 new_task.record_end_event();
515 return new_task;
516 }
517
518 template <class M, class F, class... Args>
519 cs_task
521 std::initializer_list<cs_event_ref> const &sync_events,
522 F &&f,
523 Args &&...args)
524 {
526 new_task.add_dependency(sync_events);
528 std::forward<F>(f),
529 std::forward<Args>(args)...);
530 new_task.record_end_event();
531 return new_task;
532 }
533
534 template <class M, class F, class... Args>
535 cs_task
536 parallel_for_b_faces(const M *m, F &&f, Args &&...args)
537 {
540 std::forward<F>(f),
541 std::forward<Args>(args)...);
542 new_task.record_end_event();
543 return new_task;
544 }
545
546 template <class M, class F, class... Args>
547 cs_task
549 std::initializer_list<cs_event_ref> const &sync_events,
550 F &&f,
551 Args &&...args)
552 {
554 new_task.add_dependency(sync_events);
556 std::forward<F>(f),
557 std::forward<Args>(args)...);
558 new_task.record_end_event();
559 return new_task;
560 }
561
562 template <class T, class F, class... Args>
563 cs_task
564 parallel_for_reduce_sum(cs_lnum_t n, T &sum, F &&f, Args &&...args)
565 {
568 sum,
569 std::forward<F>(f),
570 std::forward<Args>(args)...);
571 new_task.record_end_event();
572 return new_task;
573 }
574
575 template <class T, class F, class... Args>
576 cs_task
578 cs_lnum_t n,
579 std::initializer_list<cs_event_ref> const &sync_events,
580 T &sum,
581 F &&f,
582 Args &&...args)
583 {
585 new_task.add_dependency(sync_events);
587 sum,
588 std::forward<F>(f),
589 std::forward<Args>(args)...);
590 new_task.record_end_event();
591 return new_task;
592 }
593
594 template <class T, class R, class F, class... Args>
595 cs_task
596 parallel_for_reduce(cs_lnum_t n, T &r, R &reducer, F &&f, Args &&...args)
597 {
599 new_task.get_context().parallel_for_reduce(n,
600 r,
601 reducer,
602 std::forward<F>(f),
603 std::forward<Args>(args)...);
604 new_task.record_end_event();
605 return new_task;
606 }
607
608 template <class T, class R, class F, class... Args>
609 cs_task
611 std::initializer_list<cs_event_ref> const &sync_events,
612 T &r,
613 R &reducer,
614 F &&f,
615 Args &&...args)
616 {
618 new_task.add_dependency(sync_events);
619 new_task.get_context().parallel_for_reduce(n,
620 r,
621 reducer,
622 std::forward<F>(f),
623 std::forward<Args>(args)...);
624 new_task.record_end_event();
625 return new_task;
626 }
627
630 template <class FunctionType, class... Args>
632 single_task(std::initializer_list<cs_event_ref> const &sync_events,
633 FunctionType &&host_function,
634 Args &&...args)
635 {
637 std::move(host_function),
639 new_task.add_dependency(sync_events);
640 new_task.launch(std::forward<Args>(args)...);
641 new_task.record_end_event();
642 return new_task;
643 }
644
646 template <class FunctionType, class... Args>
648 single_task(FunctionType &&host_function, Args &&...args)
649 {
651 std::move(host_function),
653 new_task.launch(std::forward<Args>(args)...);
654 new_task.record_end_event();
655 return new_task;
656 }
657};
658
659/*=============================================================================
660 * Global variable definitions
661 *============================================================================*/
662
663/*=============================================================================
664 * Public function prototypes
665 *============================================================================*/
666
669#if defined(__CUDACC__) || defined(__HIPCC__)
670 // cudaEventElapsedTime gives a time in milliseconds
671 // with a resolution of around 0.5 microseconds
672 std::chrono::microseconds;
673#else
674 std::chrono::steady_clock::duration;
675#endif
676
677/*----------------------------------------------------------------------------*/
686/*----------------------------------------------------------------------------*/
687
690 cs_event_ref end)
691{
692 start->wait();
693 end->wait();
694
695#if defined(__CUDACC__)
696 // cudaEventElapsedTime gives a time in milliseconds
697 // with a resolution of around 0.5 microseconds
698 float result_ms;
699 cudaEventElapsedTime(&result_ms, ~start, ~end);
700 return cs_event_duration{ long(result_ms * 1000.f) };
701#elif defined(__HIPCC__)
702 // hipEventElapsedTime gives a time in milliseconds
703 // with a resolution of around 0.5 microseconds
704 float result_ms;
705 hipEventElapsedTime(&result_ms, ~start, ~end);
706 return cs_event_duration{ long(result_ms * 1000.f) };
707#else
708 return ~end - ~start;
709#endif
710}
711
712/*----------------------------------------------------------------------------*/
722/*----------------------------------------------------------------------------*/
723
726{
727 return cs_elapsed_time(task.get_start_event(), task.get_end_event());
728}
729
730/*----------------------------------------------------------------------------*/
731
auto parallel_for_reduce_sum(cs_lnum_t n, T &sum, F &&f, Args &&... args)
Definition: cs_dispatch.h:2169
auto parallel_for_reduce(cs_lnum_t n, T &result, R &reducer, F &&f, Args &&... args)
Definition: cs_dispatch.h:2195
auto parallel_for_b_faces(const M *m, F &&f, Args &&... args)
Definition: cs_dispatch.h:2127
auto parallel_for_i_faces(const M *m, F &&f, Args &&... args)
Definition: cs_dispatch.h:2099
auto parallel_for(cs_lnum_t n, F &&f, Args &&... args)
Definition: cs_dispatch.h:2146
void wait(void)
Wait (synchronize) until launched computations have finished.
Definition: cs_dispatch.h:2211
Definition: cs_dispatch.h:2288
Definition: cs_dispatch_queue.h:472
cs_task parallel_for_i_faces(const M *m, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:508
cs_task parallel_for_i_faces(const M *m, std::initializer_list< cs_event_ref > const &sync_events, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:520
cs_host_task< FunctionType, std::remove_reference_t< Args >... > single_task(std::initializer_list< cs_event_ref > const &sync_events, FunctionType &&host_function, Args &&...args)
Definition: cs_dispatch_queue.h:632
cs_host_task< FunctionType, std::remove_reference_t< Args >... > single_task(FunctionType &&host_function, Args &&...args)
Initiates a single thread task that runs on the host.
Definition: cs_dispatch_queue.h:648
cs_task parallel_for_reduce(cs_lnum_t n, std::initializer_list< cs_event_ref > const &sync_events, T &r, R &reducer, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:610
cs_task parallel_for_reduce_sum(cs_lnum_t n, T &sum, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:564
cs_task parallel_for(cs_lnum_t n, std::initializer_list< cs_event_ref > const &sync_events, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:492
cs_task parallel_for_reduce_sum(cs_lnum_t n, std::initializer_list< cs_event_ref > const &sync_events, T &sum, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:577
cs_task parallel_for_reduce(cs_lnum_t n, T &r, R &reducer, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:596
cs_task parallel_for_b_faces(const M *m, std::initializer_list< cs_event_ref > const &sync_events, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:548
cs_task parallel_for_b_faces(const M *m, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:536
cs_task parallel_for(cs_lnum_t n, F &&f, Args &&...args)
Definition: cs_dispatch_queue.h:480
cs_dispatch_context initializer_context
Context used to initialize tasks.
Definition: cs_dispatch_queue.h:476
Definition: cs_dispatch_queue.h:187
cs_event_ref & operator=(cs_event_ref const &) &=default
cs_event_ref & operator=(cs_event_ref &&) &=default
cs_event_ref(cs_event_ref const &other)=default
typename cs_event::underlying_type underlying_type
Definition: cs_dispatch_queue.h:191
cs_event * operator->()
Arrow operator to access members of the pointed event.
Definition: cs_dispatch_queue.h:206
underlying_type & operator~()
Definition: cs_dispatch_queue.h:221
cs_event_ref(cs_event &event)
Definition: cs_dispatch_queue.h:193
cs_event & operator*()
Dereference operator to access the pointed event.
Definition: cs_dispatch_queue.h:213
cs_event_ref(cs_event_ref &&other)=default
cs_event_ref()=delete
cs_host_task extends cs_device_task to add support for host function tasks.
Definition: cs_dispatch_queue.h:355
cs_host_task & operator=(cs_host_task &&)=default
~cs_host_task()
Wait for task termination.
Definition: cs_dispatch_queue.h:457
cs_host_task(cs_host_task const &)=delete
cs_host_task(FunctionType &&function, cs_dispatch_context context)
Definition: cs_dispatch_queue.h:385
std::tuple< Args... > args_tuple_t
Tuple type for argument storage.
Definition: cs_dispatch_queue.h:366
std::tuple< FunctionType > data_tuple_t
Definition: cs_dispatch_queue.h:374
cs_host_task & operator=(cs_host_task const &)=delete
cs_host_task(cs_host_task &&)=default
void launch(Args... args)
Definition: cs_dispatch_queue.h:405
Definition: cs_dispatch_queue.h:237
~cs_task()
Waits for task termination and destroys the associated CUDA stream.
Definition: cs_dispatch_queue.h:342
cs_task(cs_task &&)=default
cs_event_ref get_end_event()
Return a reference to the end event.
Definition: cs_dispatch_queue.h:336
cs_task(cs_dispatch_context context={})
Create a new task with a given context and initialize a new stream.
Definition: cs_dispatch_queue.h:256
cs_task(cs_task const &)=delete
cs_event_ref get_start_event()
Return a reference to the start event.
Definition: cs_dispatch_queue.h:329
cs_dispatch_context & get_context()
Return a reference to the context.
Definition: cs_dispatch_queue.h:322
cs_event_ref record_end_event()
Record an event from the task and return a cs_event_ref to it.
Definition: cs_dispatch_queue.h:305
void wait()
Wait for task completion.
Definition: cs_dispatch_queue.h:298
cs_task & operator=(cs_task &&)=default
void add_dependency(cs_event_ref event)
Add an event to wait for.
Definition: cs_dispatch_queue.h:275
cs_task & operator=(cs_task const &)=delete
void add_dependency(std::initializer_list< cs_event_ref > const &sync_events)
Definition: cs_dispatch_queue.h:287
int cs_lnum_t
local mesh entity id
Definition: cs_defs.h:325
std::chrono::steady_clock::duration cs_event_duration
Duration type for elapsed time between two events.
Definition: cs_dispatch_queue.h:674
cs_event_duration cs_elapsed_time(cs_event_ref start, cs_event_ref end)
Returns elapsed time (in microseconds) between two events.
Definition: cs_dispatch_queue.h:689
static void sum(const cs_mpi_wrapper &mpi_w, T &first, Vals &... values)
Sum values of a given datatype over a given communicator.
Definition: cs_parall.h:893
Definition: cs_dispatch_queue.h:82
cs_event(cs_event &&other)=default
~cs_event()=default
cs_event(cs_event const &other)=delete
Destructor.
underlying_type & operator~()
Return the underlying implementation.
Definition: cs_dispatch_queue.h:144
cs_event()
Constructor.
Definition: cs_dispatch_queue.h:96
cs_event & operator=(cs_event &&other)=default
void wait()
Wait upon completion.
Definition: cs_dispatch_queue.h:171
cs_event & operator=(cs_event const &)=delete
std::chrono::steady_clock::time_point underlying_type
Definition: cs_dispatch_queue.h:89
underlying_type event_impl
Definition: cs_dispatch_queue.h:92