Arcane  v3.15.3.0
Documentation utilisateur
Chargement...
Recherche...
Aucune correspondance
GenericFilterer.h
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2025 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* GenericFilterer.h (C) 2000-2025 */
9/* */
10/* Algorithme de filtrage. */
11/*---------------------------------------------------------------------------*/
12#ifndef ARCANE_ACCELERATOR_GENERICFILTERER_H
13#define ARCANE_ACCELERATOR_GENERICFILTERER_H
14/*---------------------------------------------------------------------------*/
15/*---------------------------------------------------------------------------*/
16
17#include "arcane/utils/ArrayView.h"
18#include "arcane/utils/FatalErrorException.h"
19#include "arcane/utils/NumArray.h"
20#include "arcane/utils/TraceInfo.h"
21
22#include "arcane/accelerator/core/RunQueue.h"
23
24#include "arcane/accelerator/AcceleratorGlobal.h"
25#include "arcane/accelerator/CommonUtils.h"
26#include "arcane/accelerator/RunCommandLaunchInfo.h"
28#include "arcane/accelerator/ScanImpl.h"
29#include "arcane/accelerator/MultiThreadAlgo.h"
30
31/*---------------------------------------------------------------------------*/
32/*---------------------------------------------------------------------------*/
33
34namespace Arcane::Accelerator::impl
35{
36//#define ARCANE_USE_SCAN_ONEDPL
37
38/*---------------------------------------------------------------------------*/
39/*---------------------------------------------------------------------------*/
40/*!
41 * \internal
42 * \brief Classe de base pour effectuer un filtrage.
43 *
44 * Contient les arguments nécessaires pour effectuer le filtrage.
45 */
46class ARCANE_ACCELERATOR_EXPORT GenericFilteringBase
47{
48 template <typename DataType, typename FlagType, typename OutputDataType>
49 friend class GenericFilteringFlag;
50 friend class GenericFilteringIf;
51 friend class SyclGenericFilteringImpl;
52
53 public:
54
55
56 protected:
57
59
60 protected:
61
62 Int32 _nbOutputElement();
63 void _allocate();
64 void _allocateTemporaryStorage(size_t size);
65 int* _getDeviceNbOutPointer();
66 void _copyDeviceNbOutToHostNbOut();
67 void _setCalled();
68 bool _checkEmpty(Int32 nb_value);
69
70 protected:
71
72 //! File d'exécution. Ne doit pas être nulle.
74 // Mémoire de travail pour l'algorithme de filtrage.
75 GenericDeviceStorage m_algo_storage;
76 //! Mémoire sur le device du nombre de valeurs filtrées
78 //! Mémoire hôte pour le nombre de valeurs filtrées
80 /*!
81 * \brief Indique quelle mémoire est utilisée pour le nombre de valeurs filtrées.
82 *
83 * Si vrai utilise directement \a m_host_nb_out_storage. Sinon, utilise
84 * m_device_nb_out_storage et fait une copie asynchrone après le filtrage pour
85 * recopier la valeur dans m_host_nb_out_storage.
86 */
87 bool m_use_direct_host_storage = true;
88
89 //! Indique si un appel est en cours
90 bool m_is_already_called = false;
91};
92
93/*---------------------------------------------------------------------------*/
94/*---------------------------------------------------------------------------*/
95
96#if defined(ARCANE_COMPILING_SYCL)
97//! Implémentation pour SYCL
98class SyclGenericFilteringImpl
99{
100 public:
101
102 template <typename SelectLambda, typename InputIterator, typename OutputIterator>
103 static void apply(GenericFilteringBase& s, Int32 nb_item, InputIterator input_iter,
105 {
106 RunQueue queue = s.m_queue;
107 using DataType = std::iterator_traits<OutputIterator>::value_type;
108#if defined(ARCANE_USE_SCAN_ONEDPL) && defined(__INTEL_LLVM_COMPILER)
109 sycl::queue true_queue = AcceleratorUtils::toSyclNativeStream(queue);
110 auto policy = oneapi::dpl::execution::make_device_policy(true_queue);
111 auto out_iter = oneapi::dpl::copy_if(policy, input_iter, input_iter + nb_item, output_iter, select_lambda);
114#else
119 {
120 auto command = makeCommand(queue);
121 command << RUNCOMMAND_LOOP1(iter, nb_item)
122 {
123 auto [i] = iter();
124 in_scan_data[i] = select_lambda(input_iter[i]) ? 1 : 0;
125 };
126 }
127 queue.barrier();
128 SyclScanner<false /*is_exclusive*/, Int32, ScannerSumOperator<Int32>> scanner;
129 scanner.doScan(queue, in_scan_data, out_scan_data, 0);
130 // La valeur de 'out_data' pour le dernier élément (nb_item-1) contient la taille du filtre
131 Int32 nb_output = out_scan_data[nb_item - 1];
133
134 const bool do_verbose = false;
135 if (do_verbose && nb_item < 1500)
136 for (int i = 0; i < nb_item; ++i) {
137 std::cout << "out_data i=" << i << " out_data=" << out_scan_data[i]
138 << " in_data=" << in_scan_data[i] << " value=" << input_iter[i] << "\n ";
139 }
140 // Copie depuis 'out_data' vers 'in_data' les indices correspondant au filtre
141 // Comme 'output_iter' et 'input_iter' peuvent se chevaucher, il
142 // faut faire une copie intermédiaire
143 // TODO: détecter cela et ne faire la copie que si nécessaire.
144 NumArray<DataType,MDDim1> out_copy(eMemoryRessource::Device);
145 out_copy.resize(nb_output);
146 auto out_copy_view = out_copy.to1DSpan();
147 {
148 auto command = makeCommand(queue);
149 command << RUNCOMMAND_LOOP1(iter, nb_item)
150 {
151 auto [i] = iter();
152 if (in_scan_data[i] == 1)
154 };
155 }
156 {
157 auto command = makeCommand(queue);
158 command << RUNCOMMAND_LOOP1(iter, nb_output)
159 {
160 auto [i] = iter();
162 };
163 }
164 // Obligatoire à cause de 'out_copy'. On pourra le supprimer avec une
165 // allocation temporaire.
166 queue.barrier();
167#endif
168 }
169};
170#endif
171
172/*---------------------------------------------------------------------------*/
173/*---------------------------------------------------------------------------*/
174/*!
175 * \internal
176 * \brief Classe pour effectuer un filtrage
177 *
178 * \a DataType est le type de donnée.
179 * \a FlagType est le type du tableau de filtre.
180 */
181template <typename DataType, typename FlagType, typename OutputDataType>
183{
184 public:
185
188 {
189 const Int32 nb_item = input.size();
190 if (output.size() != nb_item)
191 ARCANE_FATAL("Sizes are not equals: input={0} output={1}", nb_item, output.size());
192 [[maybe_unused]] const DataType* input_data = input.data();
193 [[maybe_unused]] DataType* output_data = output.data();
194 [[maybe_unused]] const FlagType* flag_data = flag.data();
196 RunQueue queue = s.m_queue;
198 switch (exec_policy) {
199#if defined(ARCANE_COMPILING_CUDA)
201 size_t temp_storage_size = 0;
202 cudaStream_t stream = AcceleratorUtils::toCudaNativeStream(queue);
203 // Premier appel pour connaitre la taille pour l'allocation
204 int* nb_out_ptr = nullptr;
205 ARCANE_CHECK_CUDA(::cub::DeviceSelect::Flagged(nullptr, temp_storage_size,
207
208 s._allocateTemporaryStorage(temp_storage_size);
209 nb_out_ptr = s._getDeviceNbOutPointer();
210 ARCANE_CHECK_CUDA(::cub::DeviceSelect::Flagged(s.m_algo_storage.address(), temp_storage_size,
212 s._copyDeviceNbOutToHostNbOut();
213 } break;
214#endif
215#if defined(ARCANE_COMPILING_HIP)
217 size_t temp_storage_size = 0;
218 // Premier appel pour connaitre la taille pour l'allocation
219 hipStream_t stream = AcceleratorUtils::toHipNativeStream(queue);
220 int* nb_out_ptr = nullptr;
222 nb_out_ptr, nb_item, stream));
223
224 s._allocateTemporaryStorage(temp_storage_size);
225 nb_out_ptr = s._getDeviceNbOutPointer();
226
227 ARCANE_CHECK_HIP(rocprim::select(s.m_algo_storage.address(), temp_storage_size, input_data, flag_data, output_data,
228 nb_out_ptr, nb_item, stream));
229 s._copyDeviceNbOutToHostNbOut();
230 } break;
231#endif
232#if defined(ARCANE_COMPILING_SYCL)
235 auto filter_lambda = [=](Int32 input_index) -> bool { return flag[input_index] != 0; };
238 SyclGenericFilteringImpl::apply(s, nb_item, iter2, out, filter_lambda);
239 } break;
240#endif
242 // Pas encore implémenté en multi-thread
243 [[fallthrough]];
245 Int32 index = 0;
246 for (Int32 i = 0; i < nb_item; ++i) {
247 if (flag[i] != 0) {
248 output[index] = input[i];
249 ++index;
250 }
251 }
252 s.m_host_nb_out_storage[0] = index;
253 } break;
254 default:
255 ARCANE_FATAL(getBadPolicyMessage(exec_policy));
256 }
257 }
258};
259
260/*---------------------------------------------------------------------------*/
261/*---------------------------------------------------------------------------*/
262/*!
263 * \internal
264 * \brief Classe pour effectuer un filtrage
265 *
266 * \a DataType est le type de donnée.
267 * \a FlagType est le type du tableau de filtre.
268 */
270{
271 public:
272
273 /*!
274 * \brief Applique le filtre.
275 *
276 * Si \a InPlace est vrai, alors OutputIterator vaut InputIterator et on
277 * met à jour directement \a input_iter.
278 */
279 template <bool InPlace, typename SelectLambda, typename InputIterator, typename OutputIterator>
282 {
284 RunQueue queue = s.m_queue;
286 RunCommand command = makeCommand(queue);
287 command << trace_info;
289 launch_info.beginExecute();
290 switch (exec_policy) {
291#if defined(ARCANE_COMPILING_CUDA)
293 size_t temp_storage_size = 0;
294 cudaStream_t stream = impl::CudaUtils::toNativeStream(queue);
295 // Premier appel pour connaitre la taille pour l'allocation
296 int* nb_out_ptr = nullptr;
297 if constexpr (InPlace)
298 ARCANE_CHECK_CUDA(::cub::DeviceSelect::If(nullptr, temp_storage_size,
300 select_lambda, stream));
301 else
302 ARCANE_CHECK_CUDA(::cub::DeviceSelect::If(nullptr, temp_storage_size,
304 select_lambda, stream));
305
306 s._allocateTemporaryStorage(temp_storage_size);
307 nb_out_ptr = s._getDeviceNbOutPointer();
308 if constexpr (InPlace)
309 ARCANE_CHECK_CUDA(::cub::DeviceSelect::If(s.m_algo_storage.address(), temp_storage_size,
311 select_lambda, stream));
312 else
313 ARCANE_CHECK_CUDA(::cub::DeviceSelect::If(s.m_algo_storage.address(), temp_storage_size,
315 select_lambda, stream));
316
317 s._copyDeviceNbOutToHostNbOut();
318 } break;
319#endif
320#if defined(ARCANE_COMPILING_HIP)
322 size_t temp_storage_size = 0;
323 // Premier appel pour connaitre la taille pour l'allocation
324 hipStream_t stream = impl::HipUtils::toNativeStream(queue);
325 int* nb_out_ptr = nullptr;
326 // NOTE: il n'y a pas de version spécifique de 'select' en-place.
327 // A priori il est possible que \a input_iter et \a output_iter
328 // aient la même valeur.
329 ARCANE_CHECK_HIP(rocprim::select(nullptr, temp_storage_size, input_iter, output_iter,
330 nb_out_ptr, nb_item, select_lambda, stream));
331 s._allocateTemporaryStorage(temp_storage_size);
332 nb_out_ptr = s._getDeviceNbOutPointer();
333 ARCANE_CHECK_HIP(rocprim::select(s.m_algo_storage.address(), temp_storage_size, input_iter, output_iter,
335 s._copyDeviceNbOutToHostNbOut();
336 } break;
337#endif
338#if defined(ARCANE_COMPILING_SYCL)
340 SyclGenericFilteringImpl::apply(s, nb_item, input_iter, output_iter, select_lambda);
341 } break;
342#endif
344 if (nb_item > 500) {
346 Int32 v = scanner.doFilter<InPlace>(launch_info.loopRunInfo(), nb_item, input_iter, output_iter, select_lambda);
347 s.m_host_nb_out_storage[0] = v;
348 break;
349 }
350 [[fallthrough]];
352 Int32 index = 0;
353 for (Int32 i = 0; i < nb_item; ++i) {
356 ++index;
357 ++output_iter;
358 }
359 ++input_iter;
360 }
361 s.m_host_nb_out_storage[0] = index;
362 } break;
363 default:
364 ARCANE_FATAL(getBadPolicyMessage(exec_policy));
365 }
366 launch_info.endExecute();
367 }
368};
369
370/*---------------------------------------------------------------------------*/
371/*---------------------------------------------------------------------------*/
372
373} // namespace Arcane::Accelerator::impl
374
375namespace Arcane::Accelerator
376{
377
378/*---------------------------------------------------------------------------*/
379/*---------------------------------------------------------------------------*/
380/*!
381 * \brief Algorithme générique de filtrage sur accélérateur.
382 */
385{
386
387 public:
388
389 /*!
390 * \brief Créé une instance.
391 *
392 * \pre queue!=nullptr
393 */
394 ARCANE_DEPRECATED_REASON("Y2024: Use GenericFilterer(const RunQueue&) instead")
396 {
398 m_queue = *queue;
399 _allocate();
400 }
401
402 /*!
403 * \brief Créé une instance.
404 *
405 * \pre queue!=nullptr
406 */
407 explicit GenericFilterer(const RunQueue& queue)
408 {
409 m_queue = queue;
410 _allocate();
411 }
412
413 public:
414
415 /*!
416 * \brief Applique un filtre.
417 *
418 * Filtre tous les éléments de \a input pour lesquels \a flag est différent de 0 et
419 * remplit \a output avec les valeurs filtrées. \a output doit avoir une taille assez
420 * grande pour contenir tous les éléments filtrés.
421 *
422 * L'algorithme séquentiel est le suivant:
423 *
424 * \code
425 * Int32 index = 0;
426 * for (Int32 i = 0; i < nb_item; ++i) {
427 * if (flag[i] != 0) {
428 * output[index] = input[i];
429 * ++index;
430 * }
431 * }
432 * return index;
433 * \endcode
434 *
435 * Il faut appeler la méthode nbOutputElement() pour obtenir le nombre d'éléments
436 * après filtrage.
437 */
438 template <typename InputDataType, typename OutputDataType, typename FlagType>
440 {
441 const Int32 nb_value = input.size();
442 if (output.size() != nb_value)
443 ARCANE_FATAL("Sizes are not equals: input={0} output={1}", nb_value, output.size());
444 if (flag.size() != nb_value)
445 ARCANE_FATAL("Sizes are not equals: input={0} flag={1}", nb_value, flag.size());
446
447 if (_checkEmpty(nb_value))
448 return;
449 _setCalled();
452 gf.apply(*base_ptr, input, output, flag);
453 }
454
455 /*!
456 * \brief Applique un filtre.
457 *
458 * Filtre tous les éléments de \a input pour lesquels \a select_lambda vaut \a true et
459 * remplit \a output avec les valeurs filtrées. \a output doit avoir une taille assez
460 * grande pour contenir tous les éléments filtrés.
461 * Les zones mémoire associées à \a input et \a output ne doivent pas se chevaucher.
462 *
463 * \a select_lambda doit avoir un opérateur `ARCCORE_HOST_DEVICE bool operator()(const DataType& v) const`.
464 *
465 * Par exemple la lambda suivante permet de ne garder que les éléments dont
466 * la valeur est supérieure à 569.
467 *
468 * \code
469 * auto filter_lambda = [] ARCCORE_HOST_DEVICE (const DataType& x) -> bool {
470 * return (x > 569.0);
471 * };
472 * \endcode
473 *
474 * L'algorithme séquentiel est le suivant:
475 *
476 * \code
477 * Int32 index = 0;
478 * for (Int32 i = 0; i < nb_item; ++i) {
479 * if (select_lambda(input[i])) {
480 * output[index] = input[i];
481 * ++index;
482 * }
483 * }
484 * return index;
485 * \endcode
486 *
487 * Il faut appeler la méthode nbOutputElement() pour obtenir le nombre d'éléments
488 * après filtrage.
489 */
490 template <typename DataType, typename SelectLambda>
493 {
494 const Int32 nb_value = input.size();
495 if (output.size() != nb_value)
496 ARCANE_FATAL("Sizes are not equals: input={0} output={1}", nb_value, output.size());
497 if (input.data() == output.data())
498 ARCANE_FATAL("Input and Output are the same. Use in place overload instead");
499 if (_checkEmpty(nb_value))
500 return;
501 _setCalled();
504 gf.apply<false>(*base_ptr, nb_value, input.data(), output.data(), select_lambda, trace_info);
505 }
506
507 /*!
508 * \brief Applique un filtre en place.
509 *
510 * Cette méthode est identique à applyIf(SmallSpan<const DataType>, SmallSpan<DataType>,
511 * const SelectLambda&, const TraceInfo& trace_info) mais les valeurs filtrées sont
512 * directement recopié dans le tableau \a input_output.
513 */
514 template <typename DataType, typename SelectLambda>
516 const TraceInfo& trace_info = TraceInfo())
517 {
518 const Int32 nb_value = input_output.size();
519 if (_checkEmpty(nb_value))
520 return;
521 _setCalled();
524 gf.apply<true>(*base_ptr, nb_value, input_output.data(), input_output.data(), select_lambda, trace_info);
525 }
526
527 /*!
528 * \brief Applique un filtre.
529 *
530 * Cette méthode est identique à Filterer::applyIf(SmallSpan<const DataType> input,
531 * SmallSpan<DataType> output, const SelectLambda& select_lambda) mais permet de spécifier un
532 * itérateur \a input_iter pour l'entrée et \a output_iter pour la sortie.
533 * Le nombre d'entité en entrée est donné par \a nb_value.
534 *
535 * Les zones mémoire associées à \a input_iter et \a output_iter ne doivent pas se chevaucher.
536 */
537 template <typename InputIterator, typename OutputIterator, typename SelectLambda>
540 {
541 if (_checkEmpty(nb_value))
542 return;
543 _setCalled();
547 }
548
549 /*!
550 * \brief Applique un filtre avec une sélection suivant un index.
551 *
552 * Cette méthode permet de filtrer en spécifiant une fonction lambda à la fois
553 * pour la sélection et l'affection. Le prototype de ces lambda est:
554 *
555 * \code
556 * auto select_lambda = [=] ARCCORE_HOST_DEVICE (Int32 index) -> bool;
557 * auto setter_lambda = [=] ARCCORE_HOST_DEVICE (Int32 input_index,Int32 output_index) -> void;
558 * \endcode
559 *
560 * Par exemple, si on souhaite recopier dans \a output le tableau \a input dont les
561 * valeurs sont supérieures à 25.0.
562 *
563 * \code
564 * SmallSpan<Real> input = ...;
565 * SmallSpan<Real> output = ...;
566 * auto select_lambda = [=] ARCCORE_HOST_DEVICE (Int32 index) -> bool
567 * {
568 * return input[index] > 25.0;
569 * };
570 * auto setter_lambda = [=] ARCCORE_HOST_DEVICE (Int32 input_index,Int32 output_index)
571 * {
572 * output[output_index] = input[input_index];
573 * };
574 * Arcane::Accelerator::RunQueue* queue = ...;
575 * Arcane::Accelerator::GenericFilterer filterer(queue);
576 * filterer.applyWithIndex(input.size(), select_lambda, setter_lambda);
577 * Int32 nb_out = filterer.nbOutputElement();
578 * \endcode
579 *
580 * Les zones mémoire associées aux valeurs d'entrée et de sortie ne doivent pas se chevaucher.
581 */
582 template <typename SelectLambda, typename SetterLambda>
595
596 //! Nombre d'éléments en sortie.
598 {
599 return _nbOutputElement();
600 }
601};
602
603/*---------------------------------------------------------------------------*/
604/*---------------------------------------------------------------------------*/
605
606} // namespace Arcane::Accelerator
607
608/*---------------------------------------------------------------------------*/
609/*---------------------------------------------------------------------------*/
610
611#endif
612
613/*---------------------------------------------------------------------------*/
614/*---------------------------------------------------------------------------*/
#define ARCANE_CHECK_POINTER(ptr)
Macro retournant le pointeur ptr s'il est non nul ou lancant une exception s'il est nul.
#define ARCANE_FATAL(...)
Macro envoyant une exception FatalErrorException.
Types et macros pour gérer les boucles sur les accélérateurs.
#define RUNCOMMAND_LOOP1(iter_name, x1,...)
Boucle sur accélérateur avec arguments supplémentaires pour les réductions.
Algorithme générique de filtrage sur accélérateur.
void applyIf(Int32 nb_value, InputIterator input_iter, OutputIterator output_iter, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Applique un filtre.
void applyWithIndex(Int32 nb_value, const SelectLambda &select_lambda, const SetterLambda &setter_lambda, const TraceInfo &trace_info=TraceInfo())
Applique un filtre avec une sélection suivant un index.
void applyIf(SmallSpan< DataType > input_output, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Applique un filtre en place.
void apply(SmallSpan< const InputDataType > input, SmallSpan< OutputDataType > output, SmallSpan< const FlagType > flag)
Applique un filtre.
void applyIf(SmallSpan< const DataType > input, SmallSpan< DataType > output, const SelectLambda &select_lambda, const TraceInfo &trace_info=TraceInfo())
Applique un filtre.
Int32 nbOutputElement()
Nombre d'éléments en sortie.
GenericFilterer(const RunQueue &queue)
Créé une instance.
Gestion d'une commande sur accélérateur.
File d'exécution pour un accélérateur.
void barrier() const
Bloque tant que toutes les commandes associées à la file ne sont pas terminées.
Definition RunQueue.cc:159
eExecutionPolicy executionPolicy() const
Politique d'exécution de la file.
Definition RunQueue.cc:169
DeviceStorage< int > m_device_nb_out_storage
Mémoire sur le device du nombre de valeurs filtrées.
NumArray< Int32, MDDim1 > m_host_nb_out_storage
Mémoire hôte pour le nombre de valeurs filtrées.
RunQueue m_queue
File d'exécution. Ne doit pas être nulle.
void apply(GenericFilteringBase &s, Int32 nb_item, InputIterator input_iter, OutputIterator output_iter, const SelectLambda &select_lambda, const TraceInfo &trace_info)
Applique le filtre.
Algorithmes avancée en mode multi-thread.
Itérateur sur une lambda pour positionner une valeur via un index.
Référence à une instance.
Espace de nom pour l'utilisation des accélérateurs.
RunCommand makeCommand(const RunQueue &run_queue)
Créé une commande associée à la file run_queue.
eExecutionPolicy
Politique d'exécution pour un Runner.
@ SYCL
Politique d'exécution utilisant l'environnement SYCL.
@ HIP
Politique d'exécution utilisant l'environnement HIP.
@ CUDA
Politique d'exécution utilisant l'environnement CUDA.
@ Sequential
Politique d'exécution séquentielle.
@ Thread
Politique d'exécution multi-thread.