deal.II version 9.8.0
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
parallel.h
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2009 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#ifndef dealii_parallel_h
14#define dealii_parallel_h
15
16
17#include <deal.II/base/config.h>
18
21#include <deal.II/base/mutex.h>
25
26#include <cstddef>
27#include <functional>
28#include <memory>
29#include <tuple>
30
31#ifdef DEAL_II_WITH_TASKFLOW
32
33# include <taskflow/algorithm/for_each.hpp>
34# include <taskflow/taskflow.hpp>
35#endif
36
37#ifdef DEAL_II_WITH_TBB
38# include <tbb/blocked_range.h>
39# include <tbb/parallel_for.h>
40# include <tbb/parallel_reduce.h>
41# include <tbb/partitioner.h>
42#else
43# include <boost/range/iterator_range.hpp>
44#endif
45
46#ifdef DEAL_II_HAVE_CXX20
47# include <concepts>
48#endif
49
50
51// TODO[WB]: allow calling functions to pass along a tbb::affinity_partitioner
52// object to ensure that subsequent calls use the same cache lines
53
55
56namespace parallel
57{
58 namespace internal
59 {
64 template <typename Number>
66 {
67 static const bool value = true;
68 };
69
70#ifdef __INTEL_COMPILER
71 // Disable long double SIMD instructions on ICC. This is to work around a
72 // bug that generates wrong code at least up to intel 15 (see
73 // tests/lac/vector-vector, tests/lac/intel-15-bug, and the discussion at
74 // https://github.com/dealii/dealii/issues/598).
75 template <>
76 struct EnableOpenMPSimdFor<long double>
77 {
78 static const bool value = false;
79 };
80#endif
81
82#ifdef DEAL_II_WITH_TASKFLOW
86 template <typename Iterator, typename Functor>
87 void
88 taskflow_parallel_for(Iterator x_begin,
89 Iterator x_end,
90 const Functor &functor,
91 const unsigned int grainsize)
92 {
93 tf::Executor &executor = MultithreadInfo::get_taskflow_executor();
94 tf::Taskflow taskflow;
95
96 // TODO: We have several choices for the Partitioner and we should spend
97 // some time benchmarking them:
98 // 1. StaticPartitioner(grainsize): all work items have grainsize number
99 // of items
100 // 2. GuidedPartitioner(grainsize):
101 // "The size of a partition is proportional to the number of unassigned
102 // iterations divided by the number of workers, and the size will
103 // gradually decrease to the given chunk size."
104 // 3. GuidedPartitioner(0): The default.
105 taskflow.for_each(
106 x_begin,
107 x_end,
108 [&](const auto &item) { functor(item); },
109 tf::StaticPartitioner(grainsize));
110
111 // Schedule the work. If we are within a task, we are required to use
112 // corun() without wait() to avoid a potential deadlock as described in
113 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker:
114 if (executor.this_worker_id() != -1)
115 executor.corun(taskflow);
116 else
117 executor.run(taskflow).wait();
118 }
119#endif
120
121#ifdef DEAL_II_WITH_TBB
125 template <typename Iterator, typename Functor>
126 void
127 parallel_for(Iterator x_begin,
128 Iterator x_end,
129 const Functor &functor,
130 const unsigned int grainsize)
131 {
132 tbb::parallel_for(tbb::blocked_range<Iterator>(x_begin, x_end, grainsize),
133 functor,
134 tbb::auto_partitioner());
135 }
136
137
138
142 template <typename Iterator, typename Functor>
143 void
144 parallel_for(Iterator x_begin,
145 Iterator x_end,
146 const Functor &functor,
147 const unsigned int grainsize,
148 const std::shared_ptr<tbb::affinity_partitioner> &partitioner)
149 {
150 tbb::parallel_for(tbb::blocked_range<Iterator>(x_begin, x_end, grainsize),
151 functor,
152 *partitioner);
153 }
154
155#else
156
160 template <typename Iterator, typename Functor>
161 void
162 parallel_for(Iterator x_begin,
163 Iterator x_end,
164 const Functor &functor,
165 const unsigned int)
166 {
167 functor(boost::iterator_range<Iterator>(x_begin, x_end));
168 }
169
170#endif
171 } // namespace internal
172
202 template <typename InputIterator, typename OutputIterator, typename Function>
204 (std::invocable<Function, decltype(*std::declval<InputIterator>())> &&
205 std::assignable_from<
206 decltype(*std::declval<OutputIterator>()),
207 std::invoke_result_t<Function,
208 decltype(*std::declval<InputIterator>())>>))
209 DEAL_II_DEPRECATED void transform(const InputIterator &begin_in,
210 const InputIterator &end_in,
211 OutputIterator out,
212 const Function &function,
213 const unsigned int grainsize)
214 {
215#ifdef DEAL_II_WITH_TASKFLOW
216 using Iterators = std::tuple<InputIterator, OutputIterator>;
217 using SyncIterators = SynchronousIterators<Iterators>;
218 Iterators x_begin(begin_in, out);
219 Iterators x_end(end_in, OutputIterator());
220
222 SyncIterators(x_begin),
223 SyncIterators(x_end),
224 [function](const auto &it) {
225 *std::get<1>(it) = function(*std::get<0>(it));
226 },
227 grainsize);
228
229#elif defined(DEAL_II_WITH_TBB)
230 using Iterators = std::tuple<InputIterator, OutputIterator>;
231 using SyncIterators = SynchronousIterators<Iterators>;
232 Iterators x_begin(begin_in, out);
233 Iterators x_end(end_in, OutputIterator());
235 SyncIterators(x_begin),
236 SyncIterators(x_end),
237 [function](const auto &range) {
238 for (const auto &p : range)
239 *std::get<1>(p) = function(*std::get<0>(p));
240 },
241 grainsize);
242#else
243 // make sure we don't get compiler
244 // warnings about unused arguments
245 (void)grainsize;
246
247 for (InputIterator in = begin_in; in != end_in;)
248 *out++ = function(*in++);
249#endif
250 }
251
252
253
285 template <typename InputIterator1,
286 typename InputIterator2,
287 typename OutputIterator,
288 typename Function>
290 (std::invocable<Function,
291 decltype(*std::declval<InputIterator1>()),
292 decltype(*std::declval<InputIterator2>())> &&
293 std::assignable_from<
294 decltype(*std::declval<OutputIterator>()),
295 std::invoke_result_t<Function,
296 decltype(*std::declval<InputIterator1>()),
297 decltype(*std::declval<InputIterator2>())>>))
298 DEAL_II_DEPRECATED void transform(const InputIterator1 &begin_in1,
299 const InputIterator1 &end_in1,
300 InputIterator2 in2,
301 OutputIterator out,
302 const Function &function,
303 const unsigned int grainsize)
304 {
305#ifdef DEAL_II_WITH_TASKFLOW
306 using Iterators =
307 std::tuple<InputIterator1, InputIterator2, OutputIterator>;
308 using SyncIterators = SynchronousIterators<Iterators>;
309 Iterators x_begin(begin_in1, in2, out);
310 Iterators x_end(end_in1, InputIterator2(), OutputIterator());
311
313 SyncIterators(x_begin),
314 SyncIterators(x_end),
315 [function](const auto &it) {
316 *std::get<2>(it) = function(*std::get<0>(it), *std::get<1>(it));
317 },
318 grainsize);
319
320#elif defined(DEAL_II_WITH_TBB)
321 using Iterators =
322 std::tuple<InputIterator1, InputIterator2, OutputIterator>;
323 using SyncIterators = SynchronousIterators<Iterators>;
324 Iterators x_begin(begin_in1, in2, out);
325 Iterators x_end(end_in1, InputIterator2(), OutputIterator());
327 SyncIterators(x_begin),
328 SyncIterators(x_end),
329 [function](const auto &range) {
330 for (const auto &p : range)
331 *std::get<2>(p) = function(*std::get<0>(p), *std::get<1>(p));
332 },
333 grainsize);
334
335#else
336 // make sure we don't get compiler
337 // warnings about unused arguments
338 (void)grainsize;
339
340 for (InputIterator1 in1 = begin_in1; in1 != end_in1;)
341 *out++ = function(*in1++, *in2++);
342#endif
343 }
344
345
346
380 template <typename InputIterator1,
381 typename InputIterator2,
382 typename InputIterator3,
383 typename OutputIterator,
384 typename Function>
386 (std::invocable<Function,
387 decltype(*std::declval<InputIterator1>()),
388 decltype(*std::declval<InputIterator2>()),
389 decltype(*std::declval<InputIterator3>())> &&
390 std::assignable_from<
391 decltype(*std::declval<OutputIterator>()),
392 std::invoke_result_t<Function,
393 decltype(*std::declval<InputIterator1>()),
394 decltype(*std::declval<InputIterator2>()),
395 decltype(*std::declval<InputIterator3>())>>))
396 DEAL_II_DEPRECATED void transform(const InputIterator1 &begin_in1,
397 const InputIterator1 &end_in1,
398 InputIterator2 in2,
399 InputIterator3 in3,
400 OutputIterator out,
401 const Function &function,
402 const unsigned int grainsize)
403 {
404#ifdef DEAL_II_WITH_TASKFLOW
405 using Iterators = std::
406 tuple<InputIterator1, InputIterator2, InputIterator3, OutputIterator>;
407 using SyncIterators = SynchronousIterators<Iterators>;
408 Iterators x_begin(begin_in1, in2, in3, out);
409 Iterators x_end(end_in1,
410 InputIterator2(),
411 InputIterator3(),
412 OutputIterator());
413
415 SyncIterators(x_begin),
416 SyncIterators(x_end),
417 [function](const auto &it) {
418 *std::get<3>(it) =
419 function(*std::get<0>(it), *std::get<1>(it), *std::get<2>(it));
420 },
421 grainsize);
422
423#elif defined(DEAL_II_WITH_TBB)
424 using Iterators = std::
425 tuple<InputIterator1, InputIterator2, InputIterator3, OutputIterator>;
426 using SyncIterators = SynchronousIterators<Iterators>;
427 Iterators x_begin(begin_in1, in2, in3, out);
428 Iterators x_end(end_in1,
429 InputIterator2(),
430 InputIterator3(),
431 OutputIterator());
433 SyncIterators(x_begin),
434 SyncIterators(x_end),
435 [function](const auto &range) {
436 for (const auto &p : range)
437 *std::get<3>(p) =
438 function(*std::get<0>(p), *std::get<1>(p), *std::get<2>(p));
439 },
440 grainsize);
441#else
442 // make sure we don't get compiler
443 // warnings about unused arguments
444 (void)grainsize;
445
446 for (OutputIterator in1 = begin_in1; in1 != end_in1;)
447 *out++ = function(*in1++, *in2++, *in3++);
448#endif
449 }
450
451
452 namespace internal
453 {
454#ifdef DEAL_II_WITH_TBB
461 template <typename Iterator, typename Function>
462 DEAL_II_CXX20_REQUIRES((std::invocable<Function, Iterator, Iterator>))
463 void apply_to_subranges(const tbb::blocked_range<Iterator> &range,
464 const Function &f)
465 {
466 f(range.begin(), range.end());
467 }
468#endif
469 } // namespace internal
470
471
543 template <typename Iterator, typename Function>
544 DEAL_II_CXX20_REQUIRES((std::invocable<Function, Iterator, Iterator>))
545 void apply_to_subranges(const Iterator &begin,
546 const std_cxx20::type_identity_t<Iterator> &end,
547 const Function &f,
548 const unsigned int grainsize)
549 {
550 Assert(grainsize > 0, ExcMessage("Grainsize must be a positive number."));
551
552 // Count elements in the range
553 const std::size_t n = [&]() -> auto {
554 if constexpr (std::is_integral_v<Iterator>)
555 return static_cast<std::size_t>(end - begin);
556 else
557 return static_cast<std::size_t>(std::distance(begin, end));
558 }();
559
560 const bool worth_doing_in_parallel =
561 (MultithreadInfo::n_threads() > 1) && (n > grainsize);
562
563#ifdef DEAL_II_WITH_TASKFLOW
564 if (worth_doing_in_parallel)
565 {
566 tf::Executor &executor = MultithreadInfo::get_taskflow_executor();
567 tf::Taskflow taskflow;
568
569 if constexpr (std::is_integral_v<Iterator>)
570 {
571 // Integral "iterator" types (e.g., int): use plain arithmetic
572 using integral_type = Iterator;
573
574 tf::IndexRange<integral_type> range(0, n, 1);
575
576 taskflow.for_each_by_index(
577 range,
578 [&, begin](const tf::IndexRange<integral_type> &subrange) {
579 integral_type subrange_begin =
580 begin + static_cast<integral_type>(subrange.begin());
581
582 integral_type subrange_end =
583 subrange_begin + static_cast<integral_type>(subrange.size());
584
585 f(subrange_begin, subrange_end);
586 },
587 tf::GuidedPartitioner(grainsize));
588 }
589 else
590 {
591 // Non-integral iterator types: advance from 'begin' using
592 // std::distance/std::advance
593 using diff_t =
594 typename std::iterator_traits<Iterator>::difference_type;
595
596 tf::IndexRange<std::size_t> range(0, n, 1);
597
598 taskflow.for_each_by_index(
599 range,
600 [&, begin](const tf::IndexRange<std::size_t> &subrange) {
601 Iterator subrange_begin = begin;
602 std::advance(subrange_begin,
603 static_cast<diff_t>(subrange.begin()));
604
605 Iterator subrange_end = subrange_begin;
606 std::advance(subrange_end,
607 static_cast<diff_t>(subrange.size()));
608
609 f(subrange_begin, subrange_end);
610 },
611 tf::GuidedPartitioner(grainsize));
612 }
613
614 // Schedule the work. If we are within a task, we are required to use
615 // corun() without wait() to avoid a potential deadlock as described in
616 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker:
617 if (executor.this_worker_id() != -1)
618 executor.corun(taskflow);
619 else
620 executor.run(taskflow).wait();
621
622 return;
623 }
624#elif defined(DEAL_II_WITH_TBB)
625 if (worth_doing_in_parallel)
626 {
628 begin,
629 end,
630 [&f](const tbb::blocked_range<Iterator> &range) {
632 },
633 grainsize);
634
635 return;
636 }
637#else
638 (void)worth_doing_in_parallel;
639#endif
640
641 // Without library support (taskflow or TBB) or if not worth running in
642 // parallel, we just call the function directly with the whole range:
643 f(begin, end);
644 }
645
646
647
676 {
681 virtual ~ParallelForInteger() = default;
682
691 void
692 apply_parallel(const std::size_t begin,
693 const std::size_t end,
694 const std::size_t minimum_parallel_grain_size) const;
695
702 virtual void
703 apply_to_subrange(const std::size_t, const std::size_t) const = 0;
704 };
705
706
707
774 template <typename ResultType, typename Iterator, typename Function>
776 (std::invocable<Function, Iterator, Iterator> &&
777 std::convertible_to<std::invoke_result_t<Function, Iterator, Iterator>,
778 ResultType>))
779 ResultType
781 const Iterator &begin,
782 const std_cxx20::type_identity_t<Iterator> &end,
783 const unsigned int grainsize)
784 {
785#ifndef DEAL_II_WITH_TBB
786 // make sure we don't get compiler
787 // warnings about unused arguments
788 (void)grainsize;
789
790 return f(begin, end);
791#else
792 return tbb::parallel_reduce(
793 tbb::blocked_range<Iterator>(begin, end, grainsize),
794 ResultType(0),
795 [f](const auto &range, const ResultType &starting_value) {
796 ResultType value = starting_value;
797 value += f(range.begin(), range.end());
798 return value;
799 },
800 std::plus<ResultType>(),
801 tbb::auto_partitioner());
802#endif
803 }
804
805
806 // --------------------- for loop affinity partitioner -----------------------
807
817 namespace internal
818 {
820 {
821 public:
826
827#ifdef DEAL_II_WITH_TBB
833
840 std::shared_ptr<tbb::affinity_partitioner>
842
848 void
850 const std::shared_ptr<tbb::affinity_partitioner> &p);
851
852 private:
857 std::shared_ptr<tbb::affinity_partitioner> my_partitioner;
858
863 bool in_use;
864
869#endif
870 };
871 } // namespace internal
872} // namespace parallel
873
874
875namespace internal
876{
878 {
893 extern unsigned int minimum_parallel_grain_size;
894 } // namespace VectorImplementation
895
896
898 {
904 extern unsigned int minimum_parallel_grain_size;
905 } // namespace SparseMatrixImplementation
906
907} // end of namespace internal
908
909
910/* --------------------------- inline functions ------------------------- */
911
912namespace parallel
913{
914 inline void
916 const std::size_t begin,
917 const std::size_t end,
918 const std::size_t minimum_parallel_grain_size) const
919 {
920#ifndef DEAL_II_WITH_TBB
921 // make sure we don't get compiler
922 // warnings about unused arguments
923 (void)minimum_parallel_grain_size;
924
926#else
928 begin,
929 end,
930 [this](const tbb::blocked_range<std::size_t> &range) {
931 apply_to_subrange(range.begin(), range.end());
932 },
933 minimum_parallel_grain_size);
934#endif
935 }
936
937} // end of namespace parallel
938
940
941#endif
*  iterator end()
*  *  iterator begin()
static unsigned int n_threads()
static tf::Executor & get_taskflow_executor()
std::shared_ptr< tbb::affinity_partitioner > acquire_one_partitioner()
Definition parallel.cc:89
void release_one_partitioner(const std::shared_ptr< tbb::affinity_partitioner > &p)
Definition parallel.cc:102
std::shared_ptr< tbb::affinity_partitioner > my_partitioner
Definition parallel.h:857
#define DEAL_II_DEPRECATED
Definition config.h:286
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:246
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define Assert(cond, exc)
static ::ExceptionBase & ExcMessage(std::string arg1)
const bool IsBlockVector< VectorType >::value
unsigned int minimum_parallel_grain_size
Definition parallel.cc:48
void parallel_for(Iterator x_begin, Iterator x_end, const Functor &functor, const unsigned int grainsize)
Definition parallel.h:127
void taskflow_parallel_for(Iterator x_begin, Iterator x_end, const Functor &functor, const unsigned int grainsize)
Definition parallel.h:88
void apply_to_subranges(const tbb::blocked_range< Iterator > &range, const Function &f)
Definition parallel.h:463
void apply_to_subranges(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, const Function &f, const unsigned int grainsize)
Definition parallel.h:545
void transform(const InputIterator &begin_in, const InputIterator &end_in, OutputIterator out, const Function &function, const unsigned int grainsize)
Definition parallel.h:209
ResultType accumulate_from_subranges(const Function &f, const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, const unsigned int grainsize)
Definition parallel.h:780
virtual ~ParallelForInteger()=default
virtual void apply_to_subrange(const std::size_t, const std::size_t) const =0
void apply_parallel(const std::size_t begin, const std::size_t end, const std::size_t minimum_parallel_grain_size) const
Definition parallel.h:915