deal.II version GIT relicensing-6809-ge913b9bb34 2026-09-25 17:20:01+00:00
\(\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
173 namespace internal
174 {
175#ifdef DEAL_II_WITH_TBB
182 template <typename Iterator, typename Function>
183 DEAL_II_CXX20_REQUIRES((std::invocable<Function, Iterator, Iterator>))
184 void apply_to_subranges(const tbb::blocked_range<Iterator> &range,
185 const Function &f)
186 {
187 f(range.begin(), range.end());
188 }
189#endif
190 } // namespace internal
191
192
264 template <typename Iterator, typename Function>
265 DEAL_II_CXX20_REQUIRES((std::invocable<Function, Iterator, Iterator>))
266 void apply_to_subranges(const Iterator &begin,
267 const std_cxx20::type_identity_t<Iterator> &end,
268 const Function &f,
269 const unsigned int grainsize)
270 {
271 Assert(grainsize > 0, ExcMessage("Grainsize must be a positive number."));
272
273 // Count elements in the range
274 const std::size_t n = [&]() -> auto {
275 if constexpr (std::is_integral_v<Iterator>)
276 return static_cast<std::size_t>(end - begin);
277 else
278 return static_cast<std::size_t>(std::distance(begin, end));
279 }();
280
281 const bool worth_doing_in_parallel =
282 (MultithreadInfo::n_threads() > 1) && (n > grainsize);
283
284#ifdef DEAL_II_WITH_TASKFLOW
285 if (worth_doing_in_parallel)
286 {
287 tf::Executor &executor = MultithreadInfo::get_taskflow_executor();
288 tf::Taskflow taskflow;
289
290 if constexpr (std::is_integral_v<Iterator>)
291 {
292 // Integral "iterator" types (e.g., int): use plain arithmetic
293 using integral_type = Iterator;
294
295 tf::IndexRange<integral_type> range(0, n, 1);
296
297 taskflow.for_each_by_index(
298 range,
299 [&, begin](const tf::IndexRange<integral_type> &subrange) {
300 integral_type subrange_begin =
301 begin + static_cast<integral_type>(subrange.begin());
302
303 integral_type subrange_end =
304 subrange_begin + static_cast<integral_type>(subrange.size());
305
306 f(subrange_begin, subrange_end);
307 },
308 tf::GuidedPartitioner(grainsize));
309 }
310 else
311 {
312 // Non-integral iterator types: advance from 'begin' using
313 // std::distance/std::advance
314 using diff_t =
315 typename std::iterator_traits<Iterator>::difference_type;
316
317 tf::IndexRange<std::size_t> range(0, n, 1);
318
319 taskflow.for_each_by_index(
320 range,
321 [&, begin](const tf::IndexRange<std::size_t> &subrange) {
322 Iterator subrange_begin = begin;
323 std::advance(subrange_begin,
324 static_cast<diff_t>(subrange.begin()));
325
326 Iterator subrange_end = subrange_begin;
327 std::advance(subrange_end,
328 static_cast<diff_t>(subrange.size()));
329
330 f(subrange_begin, subrange_end);
331 },
332 tf::GuidedPartitioner(grainsize));
333 }
334
335 // Schedule the work. If we are within a task, we are required to use
336 // corun() without wait() to avoid a potential deadlock as described in
337 // https://taskflow.github.io/taskflow/ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker:
338 if (executor.this_worker_id() != -1)
339 executor.corun(taskflow);
340 else
341 executor.run(taskflow).wait();
342
343 return;
344 }
345#elif defined(DEAL_II_WITH_TBB)
346 if (worth_doing_in_parallel)
347 {
349 begin,
350 end,
351 [&f](const tbb::blocked_range<Iterator> &range) {
352 internal::apply_to_subranges<Iterator, Function>(range, f);
353 },
354 grainsize);
355
356 return;
357 }
358#else
359 (void)worth_doing_in_parallel;
360#endif
361
362 // Without library support (taskflow or TBB) or if not worth running in
363 // parallel, we just call the function directly with the whole range:
364 f(begin, end);
365 }
366
367
368
397 {
402 virtual ~ParallelForInteger() = default;
403
412 void
413 apply_parallel(const std::size_t begin,
414 const std::size_t end,
415 const std::size_t minimum_parallel_grain_size) const;
416
423 virtual void
424 apply_to_subrange(const std::size_t, const std::size_t) const = 0;
425 };
426
427
428
495 template <typename ResultType, typename Iterator, typename Function>
497 (std::invocable<Function, Iterator, Iterator> &&
498 std::convertible_to<std::invoke_result_t<Function, Iterator, Iterator>,
499 ResultType>))
500 ResultType
502 const Iterator &begin,
503 const std_cxx20::type_identity_t<Iterator> &end,
504 const unsigned int grainsize)
505 {
506#ifndef DEAL_II_WITH_TBB
507 // make sure we don't get compiler
508 // warnings about unused arguments
509 (void)grainsize;
510
511 return f(begin, end);
512#else
513 return tbb::parallel_reduce(
514 tbb::blocked_range<Iterator>(begin, end, grainsize),
515 ResultType(0),
516 [f](const auto &range, const ResultType &starting_value) {
517 ResultType value = starting_value;
518 value += f(range.begin(), range.end());
519 return value;
520 },
521 std::plus<ResultType>(),
522 tbb::auto_partitioner());
523#endif
524 }
525
526
527 // --------------------- for loop affinity partitioner -----------------------
528
538 namespace internal
539 {
541 {
542 public:
547
548#ifdef DEAL_II_WITH_TBB
554
561 std::shared_ptr<tbb::affinity_partitioner>
563
569 void
571 const std::shared_ptr<tbb::affinity_partitioner> &p);
572
573 private:
578 std::shared_ptr<tbb::affinity_partitioner> my_partitioner;
579
584 bool in_use;
585
590#endif
591 };
592 } // namespace internal
593} // namespace parallel
594
595
596namespace internal
597{
598 namespace VectorImplementation
599 {
614 extern unsigned int minimum_parallel_grain_size;
615 } // namespace VectorImplementation
616
617
618 namespace SparseMatrixImplementation
619 {
625 extern unsigned int minimum_parallel_grain_size;
626 } // namespace SparseMatrixImplementation
627
628} // end of namespace internal
629
630
631/* --------------------------- inline functions ------------------------- */
632
633namespace parallel
634{
635 inline void
637 const std::size_t begin,
638 const std::size_t end,
639 const std::size_t minimum_parallel_grain_size) const
640 {
641#ifndef DEAL_II_WITH_TBB
642 // make sure we don't get compiler
643 // warnings about unused arguments
644 (void)minimum_parallel_grain_size;
645
647#else
649 begin,
650 end,
651 [this](const tbb::blocked_range<std::size_t> &range) {
652 apply_to_subrange(range.begin(), range.end());
653 },
654 minimum_parallel_grain_size);
655#endif
656 }
657
658} // end of namespace parallel
659
661
662#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:578
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_CXX20_REQUIRES(condition)
Definition config.h:249
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define Assert(cond, exc)
static ::ExceptionBase & ExcMessage(std::string arg1)
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:184
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:266
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:501
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:636