Arcane  4.2.1.0
Developer documentation
Loading...
Searching...
No Matches
TBBTaskImplementation.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 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/* TBBTaskImplementation.cc (C) 2000-2026 */
9/* */
10/* Implementation of tasks using TBB (Intel Threads Building Blocks). */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arccore/base/NotImplementedException.h"
15#include "arccore/base/IFunctor.h"
16#include "arccore/base/ForLoopRanges.h"
17#include "arccore/base/IObservable.h"
18#include "arccore/base/PlatformUtils.h"
19#include "arccore/base/FixedArray.h"
20#include "arccore/base/Profiling.h"
21#include "arccore/base/CheckedConvert.h"
22#include "arccore/base/FixedArray.h"
23#include "arccore/base/ForLoopRunInfo.h"
24#include "arccore/base/internal/DependencyInjection.h"
25
26#include "arccore/concurrency/IThreadImplementation.h"
27#include "arccore/concurrency/Task.h"
28#include "arccore/concurrency/ITaskImplementation.h"
29#include "arccore/concurrency/TaskFactory.h"
30#include "arccore/concurrency/ParallelFor.h"
31#include "arccore/concurrency/internal/TaskFactoryInternal.h"
32
33#include <new>
34#include <stack>
35#include <vector>
36#include <iostream>
37
38// This macro must be defined for the class 'blocked_rangeNd' to be available
39
40#define TBB_PREVIEW_BLOCKED_RANGE_ND 1
41
42// The macro 'ARCCORE_USE_ONETBB' is defined in CMakeLists.txt
43// if compiling with the OneTBB version 2021+
44// (https://github.com/oneapi-src/oneTBB.git)
45// Eventually, this will be the only version supported by Arcane.
46
47// Necessary to access task_scheduler_handle
48#define TBB_PREVIEW_WAITING_FOR_WORKERS 1
49#include <tbb/tbb.h>
50#include <oneapi/tbb/concurrent_set.h>
51#include <oneapi/tbb/global_control.h>
52
53#include <thread>
54#include <mutex>
55
56/*---------------------------------------------------------------------------*/
57/*---------------------------------------------------------------------------*/
58
59namespace Arcane
60{
61
63
64// TODO: use a specific memory pool to manage the
65// OneTBBTask to optimize the new/delete of instances of this class.
66// Previously, with older versions of TBB, this was managed with
67// the method 'tbb::task::allocate_child()'.
68
69/*---------------------------------------------------------------------------*/
70/*---------------------------------------------------------------------------*/
71
72#if (TBB_VERSION_MAJOR > 2022) || (TBB_VERSION_MAJOR == 2022 && TBB_VERSION_MINOR > 0) || defined __TBB_blocked_nd_range_H
73
74// The class "blocked_rangeNd" was removed in version
75// 2022.0.0 and replaced by "blocked_nd_range".
76template <typename Value, unsigned int N>
77using blocked_nd_range = tbb::blocked_nd_range<Value, N>;
78
79#else
80
81template <typename Value, unsigned int N>
82using blocked_nd_range = tbb::blocked_rangeNd<Value, N>;
83
84#endif
85
86/*---------------------------------------------------------------------------*/
87/*---------------------------------------------------------------------------*/
88
89namespace
90{
91 constexpr Int32 cache_line_size = 64;
92 // Positive if execution statistics are retrieved
93 bool isStatActive()
94 {
96 }
97
102 class ScopedExecInfo
103 {
104 public:
105
106 explicit ScopedExecInfo(const ForLoopRunInfo& run_info)
107 : m_run_info(run_info)
108 {
109 // If run_info.execInfo() is not null, we use it.
110 // This means that the caller will manage the execution statistics
111 // execution statistics. Otherwise, we use m_stat_info if execution statistics
112 // are requested.
113 ForLoopOneExecStat* ptr = run_info.execStat();
114 if (ptr) {
115 m_stat_info_ptr = ptr;
116 m_use_own_run_info = false;
117 }
118 else
119 m_stat_info_ptr = isStatActive() ? &m_stat_info : nullptr;
120 }
121 ~ScopedExecInfo()
122 {
123#ifdef PRINT_STAT_INFO
124 if (m_stat_info_ptr) {
125 bool is_valid = m_run_info.traceInfo().isValid();
126 if (!is_valid)
127 std::cout << "ADD_OWN_RUN_INFO nb_chunk=" << m_stat_info_ptr->nbChunk()
128 << " stack=" << platform::getStackTrace()
129 << "\n";
130 else
131 std::cout << "ADD_OWN_RUN_INFO nb_chunk=" << m_stat_info_ptr->nbChunk()
132 << " trace_name=" << m_run_info.traceInfo().traceInfo().name() << "\n";
133 }
134#endif
135 if (m_stat_info_ptr && m_use_own_run_info) {
136 ProfilingRegistry::_threadLocalForLoopInstance()->merge(*m_stat_info_ptr, m_run_info.traceInfo());
137 }
138 }
139
140 public:
141
142 ForLoopOneExecStat* statInfo() const { return m_stat_info_ptr; }
143 bool isOwn() const { return m_use_own_run_info; }
144
145 private:
146
147 ForLoopOneExecStat m_stat_info;
148 ForLoopOneExecStat* m_stat_info_ptr = nullptr;
149 ForLoopRunInfo m_run_info;
151 bool m_use_own_run_info = true;
152 };
153
154 /*---------------------------------------------------------------------------*/
155 /*---------------------------------------------------------------------------*/
156
157 inline int _currentTaskTreadIndex()
158 {
159 // NOTE: With OneTBB 2021, the value is no longer '0' if this method is called
160 // from a thread outside of a task_arena. With version 2021,
161 // the value is 65535.
162 // NOTE: It seems this is a bug in 2021.3.
163 return tbb::this_task_arena::current_thread_index();
164 }
165
166 inline blocked_nd_range<Int32, 1>
167 _toTBBRange(const ComplexForLoopRanges<1>& r)
168 {
169 return { { r.lowerBound<0>(), r.upperBound<0>() } };
170 }
171
172 inline blocked_nd_range<Int32, 2>
173 _toTBBRange(const ComplexForLoopRanges<2>& r)
174 {
175 return { { r.lowerBound<0>(), r.upperBound<0>() },
176 { r.lowerBound<1>(), r.upperBound<1>() } };
177 }
178
179 inline blocked_nd_range<Int32, 3>
180 _toTBBRange(const ComplexForLoopRanges<3>& r)
181 {
182 return { { r.lowerBound<0>(), r.upperBound<0>() },
183 { r.lowerBound<1>(), r.upperBound<1>() },
184 { r.lowerBound<2>(), r.upperBound<2>() } };
185 }
186
187 inline blocked_nd_range<Int32, 4>
188 _toTBBRange(const ComplexForLoopRanges<4>& r)
189 {
190 return { { r.lowerBound<0>(), r.upperBound<0>() },
191 { r.lowerBound<1>(), r.upperBound<1>() },
192 { r.lowerBound<2>(), r.upperBound<2>() },
193 { r.lowerBound<3>(), r.upperBound<3>() } };
194 }
195
196 /*---------------------------------------------------------------------------*/
197 /*---------------------------------------------------------------------------*/
198
199 inline blocked_nd_range<Int32, 2>
200 _toTBBRangeWithGrain(const blocked_nd_range<Int32, 2>& r, FixedArray<size_t, 2> grain_sizes)
201 {
202 return { { r.dim(0).begin(), r.dim(0).end(), grain_sizes[0] },
203 { r.dim(1).begin(), r.dim(1).end(), grain_sizes[1] } };
204 }
205
206 inline blocked_nd_range<Int32, 3>
207 _toTBBRangeWithGrain(const blocked_nd_range<Int32, 3>& r, FixedArray<size_t, 3> grain_sizes)
208 {
209 return { { r.dim(0).begin(), r.dim(0).end(), grain_sizes[0] },
210 { r.dim(1).begin(), r.dim(1).end(), grain_sizes[1] },
211 { r.dim(2).begin(), r.dim(2).end(), grain_sizes[2] } };
212 }
213
214 inline blocked_nd_range<Int32, 4>
215 _toTBBRangeWithGrain(const blocked_nd_range<Int32, 4>& r, FixedArray<size_t, 4> grain_sizes)
216 {
217 return { { r.dim(0).begin(), r.dim(0).end(), grain_sizes[0] },
218 { r.dim(1).begin(), r.dim(1).end(), grain_sizes[1] },
219 { r.dim(2).begin(), r.dim(2).end(), grain_sizes[2] },
220 { r.dim(3).begin(), r.dim(3).end(), grain_sizes[3] } };
221 }
222
223 /*---------------------------------------------------------------------------*/
224 /*---------------------------------------------------------------------------*/
225
227 _fromTBBRange(const blocked_nd_range<Int32, 2>& r)
228 {
229 using BoundsType = ArrayBounds<MDDim2>;
230 using ArrayExtentType = BoundsType::ArrayExtentType;
231
232 BoundsType lower_bounds(ArrayExtentType(r.dim(0).begin(), r.dim(1).begin()));
233 auto s0 = static_cast<Int32>(r.dim(0).size());
234 auto s1 = static_cast<Int32>(r.dim(1).size());
235 BoundsType sizes(ArrayExtentType(s0, s1));
236 return { lower_bounds, sizes };
237 }
238
240 _fromTBBRange(const blocked_nd_range<Int32, 3>& r)
241 {
242 using BoundsType = ArrayBounds<MDDim3>;
243 using ArrayExtentType = BoundsType::ArrayExtentType;
244
245 BoundsType lower_bounds(ArrayExtentType(r.dim(0).begin(), r.dim(1).begin(), r.dim(2).begin()));
246 auto s0 = static_cast<Int32>(r.dim(0).size());
247 auto s1 = static_cast<Int32>(r.dim(1).size());
248 auto s2 = static_cast<Int32>(r.dim(2).size());
249 BoundsType sizes(ArrayExtentType(s0, s1, s2));
250 return { lower_bounds, sizes };
251 }
252
254 _fromTBBRange(const blocked_nd_range<Int32, 4>& r)
255 {
256 using BoundsType = ArrayBounds<MDDim4>;
257 using ArrayExtentType = typename BoundsType::ArrayExtentType;
258
259 BoundsType lower_bounds(ArrayExtentType(r.dim(0).begin(), r.dim(1).begin(), r.dim(2).begin(), r.dim(3).begin()));
260 auto s0 = static_cast<Int32>(r.dim(0).size());
261 auto s1 = static_cast<Int32>(r.dim(1).size());
262 auto s2 = static_cast<Int32>(r.dim(2).size());
263 auto s3 = static_cast<Int32>(r.dim(3).size());
264 BoundsType sizes(ArrayExtentType(s0, s1, s2, s3));
265 return { lower_bounds, sizes };
266 }
267
268} // namespace
269
270/*---------------------------------------------------------------------------*/
271/*---------------------------------------------------------------------------*/
272
273class OneTBBTaskFunctor
274{
275 public:
276
277 OneTBBTaskFunctor(ITaskFunctor* functor, ITask* task)
278 : m_functor(functor)
279 , m_task(task)
280 {}
281
282 public:
283
284 void operator()() const
285 {
286 if (m_functor) {
287 ITaskFunctor* tf = m_functor;
288 m_functor = nullptr;
289 TaskContext task_context(m_task);
290 //cerr << "FUNC=" << typeid(*tf).name();
291 tf->executeFunctor(task_context);
292 }
293 }
294
295 public:
296
297 mutable ITaskFunctor* m_functor;
298 ITask* m_task;
299};
300
301/*---------------------------------------------------------------------------*/
302/*---------------------------------------------------------------------------*/
303
304class OneTBBTask
305: public ITask
306{
307 public:
308
309 static const int FUNCTOR_CLASS_SIZE = 32;
310
311 public:
312
313 explicit OneTBBTask(ITaskFunctor* f)
314 : m_functor(f)
315 {
316 m_functor = f->clone(m_functor_buf.data(), FUNCTOR_CLASS_SIZE);
317 }
318
319 public:
320
321 OneTBBTaskFunctor taskFunctor() { return OneTBBTaskFunctor(m_functor, this); }
322 void launchAndWait() override;
323 void launchAndWait(ConstArrayView<ITask*> tasks) override;
324
325 protected:
326
327 ITask* _createChildTask(ITaskFunctor* functor) override;
328
329 public:
330
331 ITaskFunctor* m_functor = nullptr;
333};
334using TBBTask = OneTBBTask;
335
336/*---------------------------------------------------------------------------*/
337/*---------------------------------------------------------------------------*/
338
339/*
340 * Do not use the local observer on the task_arena.
341 * Use the global observer on the scheduler.
342 * For the ID, use tbb::this_task_arena::current_thread_index().
343 */
344class TBBTaskImplementation
345: public ITaskImplementation
346{
347 class Impl;
348 class ParallelForExecute;
349 template <int RankValue>
351
352 public:
353
354 // For performance reasons, aligns to a cache line
355 // and uses padding.
356 class ARCCORE_ALIGNAS_PACKED(64) TaskThreadInfo
357 {
358 public:
359
360 TaskThreadInfo()
361 : m_task_index(-1)
362 {}
363
364 public:
365
366 void setTaskIndex(Integer v) { m_task_index = v; }
367 Integer taskIndex() const { return m_task_index; }
368
369 private:
370
371 Integer m_task_index;
372 };
373
381 class TaskInfoLockGuard
382 {
383 public:
384
385 TaskInfoLockGuard(TaskThreadInfo* tti, Integer task_index)
386 : m_tti(tti)
387 , m_old_task_index(-1)
388 {
389 if (tti) {
390 m_old_task_index = tti->taskIndex();
391 tti->setTaskIndex(task_index);
392 }
393 }
394 ~TaskInfoLockGuard()
395 {
396 if (m_tti)
397 m_tti->setTaskIndex(m_old_task_index);
398 }
399
400 private:
401
402 TaskThreadInfo* m_tti;
403 Integer m_old_task_index;
404 };
405
406 public:
407
408 TBBTaskImplementation() = default;
409 ~TBBTaskImplementation() override;
410
411 public:
412
413 void build() {}
414 void initialize(Int32 nb_thread) override;
415 void terminate() override;
416
418 {
419 OneTBBTask* t = new OneTBBTask(f);
420 return t;
421 }
422
423 void executeParallelFor(Int32 begin, Int32 size, const ParallelLoopOptions& options, IRangeFunctor* f) final;
424 void executeParallelFor(Int32 begin, Int32 size, Integer grain_size, IRangeFunctor* f) final;
425 void executeParallelFor(Int32 begin, Int32 size, IRangeFunctor* f) final
426 {
428 }
429 void executeParallelFor(const ParallelFor1DLoopInfo& loop_info) override;
430
432 const ForLoopRunInfo& run_info,
433 IMDRangeFunctor<1>* functor) final
434 {
435 _executeMDParallelFor<1>(loop_ranges, functor, run_info);
436 }
438 const ForLoopRunInfo& run_info,
439 IMDRangeFunctor<2>* functor) final
440 {
441 _executeMDParallelFor<2>(loop_ranges, functor, run_info);
442 }
444 const ForLoopRunInfo& run_info,
445 IMDRangeFunctor<3>* functor) final
446 {
447 _executeMDParallelFor<3>(loop_ranges, functor, run_info);
448 }
450 const ForLoopRunInfo& run_info,
451 IMDRangeFunctor<4>* functor) final
452 {
453 _executeMDParallelFor<4>(loop_ranges, functor, run_info);
454 }
455
456 bool isActive() const final
457 {
458 return m_is_active;
459 }
460
462 {
463 return (nbAllowedThread() <= 1) ? 0 : _currentTaskTreadIndex();
464 }
465
466 Int32 currentTaskIndex() const final;
467
468 void printInfos(std::ostream& o) const final;
469
470 public:
471
478 TaskThreadInfo* currentTaskThreadInfo() const;
479
480 private:
481
482 bool m_is_active = false;
483 Impl* m_p = nullptr;
484
485 private:
486
487 template <int RankValue> void
488 _executeMDParallelFor(const ComplexForLoopRanges<RankValue>& loop_ranges,
489 IMDRangeFunctor<RankValue>* functor,
490 const ForLoopRunInfo& run_info);
491 void _executeParallelFor(const ParallelFor1DLoopInfo& loop_info);
492};
493
494/*---------------------------------------------------------------------------*/
495/*---------------------------------------------------------------------------*/
496
497class TBBTaskImplementation::Impl
498{
499 class TaskObserver
500 : public tbb::task_scheduler_observer
501 {
502 public:
503
504 explicit TaskObserver(TBBTaskImplementation::Impl* p)
505 : tbb::task_scheduler_observer(p->m_main_arena)
506 , m_p(p)
507 {
508 }
509 void on_scheduler_entry(bool is_worker) override
510 {
511 m_p->notifyThreadCreated(is_worker);
512 }
513 void on_scheduler_exit(bool is_worker) override
514 {
515 m_p->notifyThreadDestroyed(is_worker);
516 }
518 };
519
520 public:
521
522 Impl()
523 : m_task_observer(this)
524 , m_thread_task_infos(cache_line_size)
525 {
526 m_nb_allowed_thread = tbb::info::default_concurrency();
527 _init();
528 }
529 Impl(Int32 nb_thread)
530 : m_main_arena(nb_thread)
531 , m_task_observer(this)
532 , m_thread_task_infos(cache_line_size)
533 {
534 m_nb_allowed_thread = nb_thread;
535 _init();
536 }
537
538 public:
539
540 Int32 nbAllowedThread() const { return m_nb_allowed_thread; }
541 TaskThreadInfo* threadTaskInfo(Integer index) { return &m_thread_task_infos[index]; }
542
543 private:
544
545 Int32 m_nb_allowed_thread = 0;
546
547 public:
548
549 void terminate()
550 {
551 for (auto x : m_sub_arena_list) {
552 if (x)
553 x->terminate();
554 delete x;
555 }
556 m_sub_arena_list.clear();
557 m_main_arena.terminate();
558 m_task_observer.observe(false);
559 oneapi::tbb::finalize(m_task_scheduler_handle);
560 }
561
562 public:
563
564 void notifyThreadCreated(bool is_worker)
565 {
566 std::thread::id my_thread_id = std::this_thread::get_id();
567
568 // With OneTBB, this method is called every time we enter
569 // our 'task_arena'. Since the notification method should only be called once,
570 // we use a set to keep track of the threads already created.
571 // NOTE: This method cannot be used with the historical TBB version
572 // (2018) because this 'contains' method does not exist
573 if (m_constructed_thread_map.contains(my_thread_id))
574 return;
575 m_constructed_thread_map.insert(my_thread_id);
576
577 {
578 if (TaskFactory::verboseLevel() >= 1) {
579 std::ostringstream ostr;
580 ostr << "TBB: CREATE THREAD"
581 << " nb_allowed=" << m_nb_allowed_thread
582 << " tbb_default_allowed=" << tbb::info::default_concurrency()
583 << " id=" << my_thread_id
584 << " arena_id=" << _currentTaskTreadIndex()
585 << " is_worker=" << is_worker
586 << "\n";
587 std::cout << ostr.str();
588 }
590 }
591 }
592
593 void notifyThreadDestroyed([[maybe_unused]] bool is_worker)
594 {
595 // With OneTBB, this method is called every time we exit
596 // the main arena. Therefore, it does not truly correspond to a
597 // thread destruction. So we do nothing for this notification.
598 // TODO: Look into how we can be notified of the actual thread destruction.
599 }
600
601 private:
602
603#if TBB_VERSION_MAJOR > 2021 || (TBB_VERSION_MAJOR == 2021 && TBB_VERSION_MINOR > 5)
604 oneapi::tbb::task_scheduler_handle m_task_scheduler_handle = oneapi::tbb::attach();
605#else
606 oneapi::tbb::task_scheduler_handle m_task_scheduler_handle = tbb::task_scheduler_handle::get();
607#endif
608
609 public:
610
611 tbb::task_arena m_main_arena;
613 std::vector<tbb::task_arena*> m_sub_arena_list;
614
615 private:
616
617 TaskObserver m_task_observer;
618 std::mutex m_thread_created_mutex;
619 std::vector<TaskThreadInfo> m_thread_task_infos;
620 tbb::concurrent_set<std::thread::id> m_constructed_thread_map;
621 void _init()
622 {
623 ConcurrencyBase::_setMaxAllowedThread(m_nb_allowed_thread);
624
625 if (TaskFactory::verboseLevel() >= 1) {
626 std::cout << "TBB: TBBTaskImplementationInit nb_allowed_thread=" << m_nb_allowed_thread
627 << " id=" << std::this_thread::get_id()
628 << " version=" << TBB_VERSION_MAJOR << "." << TBB_VERSION_MINOR
629 << "\n";
630 }
631 m_thread_task_infos.resize(m_nb_allowed_thread);
632 m_task_observer.observe(true);
633 Integer max_arena_size = m_nb_allowed_thread;
634 // Artificially limit the number of tbb::task_arena
635 // to avoid having too many allocated objects.
636 if (max_arena_size > 512)
637 max_arena_size = 512;
638 if (max_arena_size < 2)
639 max_arena_size = 2;
640 m_sub_arena_list.resize(max_arena_size);
641 m_sub_arena_list[0] = m_sub_arena_list[1] = nullptr;
642 for (Integer i = 2; i < max_arena_size; ++i)
643 m_sub_arena_list[i] = new tbb::task_arena(i);
644 }
645};
646
647/*---------------------------------------------------------------------------*/
648/*---------------------------------------------------------------------------*/
649
653class TBBParallelFor
654{
655 public:
656
657 TBBParallelFor(IRangeFunctor* f, Int32 nb_allowed_thread, ForLoopOneExecStat* stat_info)
658 : m_functor(f)
659 , m_stat_info(stat_info)
660 , m_nb_allowed_thread(nb_allowed_thread)
661 {}
662
663 public:
664
665 void operator()(tbb::blocked_range<Integer>& range) const
666 {
667#ifdef ARCCORE_CHECK
668 if (TaskFactory::verboseLevel() >= 3) {
669 std::ostringstream o;
670 o << "TBB: INDEX=" << TaskFactory::currentTaskThreadIndex()
671 << " id=" << std::this_thread::get_id()
672 << " max_allowed=" << m_nb_allowed_thread
673 << " range_begin=" << range.begin() << " range_size=" << range.size()
674 << "\n";
675 std::cout << o.str();
676 std::cout.flush();
677 }
678
679 int tbb_index = _currentTaskTreadIndex();
680 if (tbb_index < 0 || tbb_index >= m_nb_allowed_thread)
681 ARCCORE_FATAL("Invalid index for thread idx={0} valid_interval=[0..{1}[",
682 tbb_index, m_nb_allowed_thread);
683#endif
684
685 if (m_stat_info)
686 m_stat_info->incrementNbChunk();
687 m_functor->executeFunctor(range.begin(), CheckedConvert::toInteger(range.size()));
688 }
689
690 private:
691
692 IRangeFunctor* m_functor;
693 ForLoopOneExecStat* m_stat_info = nullptr;
694 Int32 m_nb_allowed_thread;
695};
696
697/*---------------------------------------------------------------------------*/
698/*---------------------------------------------------------------------------*/
699
703template <int RankValue>
704class TBBMDParallelFor
705{
706 public:
707
708 TBBMDParallelFor(IMDRangeFunctor<RankValue>* f, Int32 nb_allowed_thread, ForLoopOneExecStat* stat_info)
709 : m_functor(f)
710 , m_stat_info(stat_info)
711 , m_nb_allowed_thread(nb_allowed_thread)
712 {}
713
714 public:
715
716 void operator()(blocked_nd_range<Int32, RankValue>& range) const
717 {
718#ifdef ARCCORE_CHECK
719 if (TaskFactory::verboseLevel() >= 3) {
720 std::ostringstream o;
721 o << "TBB: INDEX=" << TaskFactory::currentTaskThreadIndex()
722 << " id=" << std::this_thread::get_id()
723 << " max_allowed=" << m_nb_allowed_thread
724 << " MDFor ";
725 for (Int32 i = 0; i < RankValue; ++i) {
726 auto r0 = static_cast<Int32>(range.dim(i).begin());
727 auto r1 = static_cast<Int32>(range.dim(i).size());
728 o << " range" << i << " (begin=" << r0 << " size=" << r1 << ")";
729 }
730 o << "\n";
731 std::cout << o.str();
732 std::cout.flush();
733 }
734
735 int tbb_index = _currentTaskTreadIndex();
736 if (tbb_index < 0 || tbb_index >= m_nb_allowed_thread)
737 ARCCORE_FATAL("Invalid index for thread idx={0} valid_interval=[0..{1}[",
738 tbb_index, m_nb_allowed_thread);
739#endif
740
741 if (m_stat_info)
742 m_stat_info->incrementNbChunk();
743 m_functor->executeFunctor(_fromTBBRange(range));
744 }
745
746 private:
747
748 IMDRangeFunctor<RankValue>* m_functor = nullptr;
749 ForLoopOneExecStat* m_stat_info = nullptr;
750 Int32 m_nb_allowed_thread;
751};
752
753/*---------------------------------------------------------------------------*/
754/*---------------------------------------------------------------------------*/
755
774class TBBDeterministicParallelFor
775{
776 public:
777
778 TBBDeterministicParallelFor(TBBTaskImplementation* impl, const TBBParallelFor& tbb_for,
779 Integer begin_index, Integer size, Integer grain_size, Integer nb_thread)
780 : m_impl(impl)
781 , m_tbb_for(tbb_for)
782 , m_nb_thread(nb_thread)
783 , m_begin_index(begin_index)
784 , m_size(size)
785 , m_grain_size(grain_size)
786 , m_nb_block(0)
787 , m_block_size(0)
788 , m_nb_block_per_thread(0)
789 {
790 if (m_nb_thread < 1)
791 m_nb_thread = 1;
792
793 if (m_grain_size > 0) {
794 m_block_size = m_grain_size;
795 if (m_block_size > 0) {
796 m_nb_block = m_size / m_block_size;
797 if ((m_size % m_block_size) != 0)
798 ++m_nb_block;
799 }
800 else
801 m_nb_block = 1;
802 m_nb_block_per_thread = m_nb_block / m_nb_thread;
803 if ((m_nb_block % m_nb_thread) != 0)
804 ++m_nb_block_per_thread;
805 }
806 else {
807 if (m_nb_block < 1)
808 m_nb_block = m_nb_thread;
809 m_block_size = m_size / m_nb_block;
810 m_nb_block_per_thread = 1;
811 }
812 if (TaskFactory::verboseLevel() >= 2) {
813 std::cout << "TBBDeterministicParallelFor: BEGIN=" << m_begin_index << " size=" << m_size
814 << " grain_size=" << m_grain_size
815 << " nb_block=" << m_nb_block << " nb_thread=" << m_nb_thread
816 << " nb_block_per_thread=" << m_nb_block_per_thread
817 << " block_size=" << m_block_size
818 << " block_size*nb_block=" << m_block_size * m_nb_block << '\n';
819 }
820 }
821
822 public:
823
830 void operator()(tbb::blocked_range<Integer>& range) const
831 {
832 auto nb_iter = static_cast<Integer>(range.size());
833 for (Integer i = 0; i < nb_iter; ++i) {
834 Integer task_id = range.begin() + i;
835 for (Integer k = 0, kn = m_nb_block_per_thread; k < kn; ++k) {
836 Integer block_id = task_id + (k * m_nb_thread);
837 if (block_id < m_nb_block)
838 _doBlock(task_id, block_id);
839 }
840 }
841 }
842
843 void _doBlock(Integer task_id, Integer block_id) const
844 {
845 TBBTaskImplementation::TaskInfoLockGuard guard(m_impl->currentTaskThreadInfo(), task_id);
846
847 Integer iter_begin = block_id * m_block_size;
848 Integer iter_size = m_block_size;
849 if ((block_id + 1) == m_nb_block) {
850 // For the last block, the size is the number of remaining elements
851 iter_size = m_size - iter_begin;
852 }
853 iter_begin += m_begin_index;
854#ifdef ARCCORE_CHECK
855 if (TaskFactory::verboseLevel() >= 3) {
856 std::ostringstream o;
857 o << "TBB: DoBlock: BLOCK task_id=" << task_id << " block_id=" << block_id
858 << " iter_begin=" << iter_begin << " iter_size=" << iter_size << '\n';
859 std::cout << o.str();
860 std::cout.flush();
861 }
862#endif
863 if (iter_size > 0) {
864 auto r = tbb::blocked_range<int>(iter_begin, iter_begin + iter_size);
865 m_tbb_for(r);
866 }
867 }
868
869 private:
870
871 TBBTaskImplementation* m_impl;
872 const TBBParallelFor& m_tbb_for;
873 Integer m_nb_thread;
874 Integer m_begin_index;
875 Integer m_size;
876 Integer m_grain_size;
877 Integer m_nb_block;
878 Integer m_block_size;
879 Integer m_nb_block_per_thread;
880};
881
882/*---------------------------------------------------------------------------*/
883/*---------------------------------------------------------------------------*/
884
886{
887 public:
888
889 ParallelForExecute(TBBTaskImplementation* impl, const ParallelLoopOptions& options,
890 Integer begin, Integer size, IRangeFunctor* f, ForLoopOneExecStat* stat_info)
891 : m_impl(impl)
892 , m_begin(begin)
893 , m_size(size)
894 , m_functor(f)
895 , m_options(options)
896 , m_stat_info(stat_info)
897 {}
898
899 public:
900
901 void operator()() const
902 {
903 Integer nb_thread = m_options.maxThread();
904 TBBParallelFor pf(m_functor, nb_thread, m_stat_info);
905 Integer gsize = m_options.grainSize();
906 tbb::blocked_range<Integer> range(m_begin, m_begin + m_size);
907 if (TaskFactory::verboseLevel() >= 1)
908 std::cout << "TBB: TBBTaskImplementationInit ParallelForExecute begin=" << m_begin
909 << " size=" << m_size << " gsize=" << gsize
910 << " partitioner=" << (int)m_options.partitioner()
911 << " nb_thread=" << nb_thread
912 << " has_stat_info=" << (m_stat_info != nullptr)
913 << '\n';
914
915 if (gsize > 0)
916 range = tbb::blocked_range<Integer>(m_begin, m_begin + m_size, gsize);
917
918 if (m_options.partitioner() == ParallelLoopOptions::Partitioner::Static) {
919 tbb::parallel_for(range, pf, tbb::static_partitioner());
920 }
921 else if (m_options.partitioner() == ParallelLoopOptions::Partitioner::Deterministic) {
922 tbb::blocked_range<Integer> range2(0, nb_thread, 1);
923 TBBDeterministicParallelFor dpf(m_impl, pf, m_begin, m_size, gsize, nb_thread);
924 tbb::parallel_for(range2, dpf);
925 }
926 else
927 tbb::parallel_for(range, pf);
928 }
929
930 private:
931
932 TBBTaskImplementation* m_impl = nullptr;
933 Integer m_begin;
934 Integer m_size;
935 IRangeFunctor* m_functor = nullptr;
936 ParallelLoopOptions m_options;
937 ForLoopOneExecStat* m_stat_info = nullptr;
938};
939
940/*---------------------------------------------------------------------------*/
941/*---------------------------------------------------------------------------*/
942
943template <int RankValue>
945{
946 public:
947
948 MDParallelForExecute(TBBTaskImplementation* impl,
949 const ParallelLoopOptions& options,
951 IMDRangeFunctor<RankValue>* f, [[maybe_unused]] ForLoopOneExecStat* stat_info)
952 : m_impl(impl)
953 , m_tbb_range(_toTBBRange(range))
954 , m_functor(f)
955 , m_options(options)
956 , m_stat_info(stat_info)
957 {
958 // We cannot modify the values of a tbb::blocked_rangeNd instance.
959 // We must therefore reconstruct it completely.
960 FixedArray<size_t, RankValue> all_grain_sizes;
961 Int32 gsize = m_options.grainSize();
962 if (gsize > 0) {
963 // If the grain size is not zero, it must be distributed
964 // across all dimensions. We start with the last one.
965 // TODO: check why performance is sometimes
966 // lower than what we get using a static partitioner.
967 constexpr bool is_verbose = false;
968 std::array<Int32, RankValue> range_extents = range.extents().asStdArray();
969 double ratio = static_cast<double>(gsize) / static_cast<double>(range.nbElement());
970 if constexpr (is_verbose) {
971 std::cout << "GSIZE=" << gsize << " rank=" << RankValue << " ratio=" << ratio;
972 for (Int32 i = 0; i < RankValue; ++i)
973 std::cout << " range" << i << "=" << range_extents[i];
974 std::cout << "\n";
975 }
976 Int32 index = RankValue - 1;
977 Int32 remaining_grain = gsize;
978 for (; index >= 0; --index) {
979 Int32 current = range_extents[index];
980 if constexpr (is_verbose)
981 std::cout << "Check index=" << index << " remaining=" << remaining_grain << " current=" << current << "\n";
982 if (remaining_grain > current) {
983 all_grain_sizes[index] = current;
984 remaining_grain /= current;
985 }
986 else {
987 all_grain_sizes[index] = remaining_grain;
988 break;
989 }
990 }
991 for (Int32 i = 0; i < index; ++i)
992 all_grain_sizes[i] = 1;
993 if constexpr (is_verbose) {
994 for (Int32 i = 0; i < RankValue; ++i)
995 std::cout << " grain" << i << "=" << all_grain_sizes[i];
996 std::cout << "\n";
997 }
998 m_tbb_range = _toTBBRangeWithGrain(m_tbb_range, all_grain_sizes);
999 }
1000 }
1001
1002 public:
1003
1004 void operator()() const
1005 {
1006 Integer nb_thread = m_options.maxThread();
1007 TBBMDParallelFor<RankValue> pf(m_functor, nb_thread, m_stat_info);
1008
1009 if (m_options.partitioner() == ParallelLoopOptions::Partitioner::Static) {
1010 tbb::parallel_for(m_tbb_range, pf, tbb::static_partitioner());
1011 }
1012 else if (m_options.partitioner() == ParallelLoopOptions::Partitioner::Deterministic) {
1013 // TODO: implement deterministic mode
1014 ARCCORE_THROW(NotImplementedException, "ParallelLoopOptions::Partitioner::Deterministic for multi-dimensionnal loops");
1015 //tbb::blocked_range<Integer> range2(0,nb_thread,1);
1016 //TBBDeterministicParallelFor dpf(m_impl,pf,m_begin,m_size,gsize,nb_thread);
1017 //tbb::parallel_for(range2,dpf);
1018 }
1019 else {
1020 tbb::parallel_for(m_tbb_range, pf);
1021 }
1022 }
1023
1024 private:
1025
1026 TBBTaskImplementation* m_impl = nullptr;
1027 blocked_nd_range<Int32, RankValue> m_tbb_range;
1028 IMDRangeFunctor<RankValue>* m_functor = nullptr;
1029 ParallelLoopOptions m_options;
1030 ForLoopOneExecStat* m_stat_info = nullptr;
1031};
1032
1033/*---------------------------------------------------------------------------*/
1034/*---------------------------------------------------------------------------*/
1035
1036TBBTaskImplementation::
1037~TBBTaskImplementation()
1038{
1039 delete m_p;
1040}
1041
1042/*---------------------------------------------------------------------------*/
1043/*---------------------------------------------------------------------------*/
1044
1046initialize(Int32 nb_thread)
1047{
1048 if (nb_thread < 0)
1049 nb_thread = 0;
1050 m_is_active = (nb_thread != 1);
1051 if (nb_thread != 0)
1052 m_p = new Impl(nb_thread);
1053 else
1054 m_p = new Impl();
1058}
1059
1060/*---------------------------------------------------------------------------*/
1061/*---------------------------------------------------------------------------*/
1062
1064terminate()
1065{
1066 m_p->terminate();
1067}
1068
1069/*---------------------------------------------------------------------------*/
1070/*---------------------------------------------------------------------------*/
1071
1073printInfos(std::ostream& o) const
1074{
1075 o << "OneTBBTaskImplementation"
1076 << " version=" << TBB_VERSION_STRING
1077 << " interface=" << TBB_INTERFACE_VERSION
1078 << " runtime_interface=" << TBB_runtime_interface_version();
1079}
1080
1081/*---------------------------------------------------------------------------*/
1082/*---------------------------------------------------------------------------*/
1083
1084void TBBTaskImplementation::
1085_executeParallelFor(const ParallelFor1DLoopInfo& loop_info)
1086{
1087 ScopedExecInfo sei(loop_info.runInfo());
1088 ForLoopOneExecStat* stat_info = sei.statInfo();
1089 ::Arcane::Impl::ScopedStatLoop scoped_loop(sei.isOwn() ? stat_info : nullptr);
1090
1091 Int32 begin = loop_info.beginIndex();
1092 Int32 size = loop_info.size();
1093 ParallelLoopOptions options = loop_info.runInfo().options().value_or(TaskFactory::defaultParallelLoopOptions());
1094 IRangeFunctor* f = loop_info.functor();
1096
1097 Integer max_thread = options.maxThread();
1098 Integer nb_allowed_thread = m_p->nbAllowedThread();
1099 if (max_thread < 0)
1100 max_thread = nb_allowed_thread;
1101
1102 if (TaskFactory::verboseLevel() >= 1)
1103 std::cout << "TBB: TBBTaskImplementation executeParallelFor begin=" << begin
1104 << " size=" << size << " max_thread=" << max_thread
1105 << " grain_size=" << options.grainSize()
1106 << " nb_allowed=" << nb_allowed_thread << '\n';
1107
1108 // In sequential execution, call the method \a f directly.
1109 if (max_thread == 1 || max_thread == 0) {
1110 f->executeFunctor(begin, size);
1111 return;
1112 }
1113
1114 // Replace the uninitialized values of \a options with those of \a m_default_loop_options
1115 ParallelLoopOptions true_options(options);
1116 true_options.mergeUnsetValues(TaskFactory::defaultParallelLoopOptions());
1117 true_options.setMaxThread(max_thread);
1118
1119 ParallelForExecute pfe(this, true_options, begin, size, f, stat_info);
1120
1121 tbb::task_arena* used_arena = nullptr;
1122 if (max_thread < nb_allowed_thread && max_thread < m_p->m_sub_arena_list.size())
1123 used_arena = m_p->m_sub_arena_list[max_thread];
1124 if (!used_arena)
1125 used_arena = &(m_p->m_main_arena);
1126 used_arena->execute(pfe);
1127}
1128
1129/*---------------------------------------------------------------------------*/
1130/*---------------------------------------------------------------------------*/
1131
1134{
1135 _executeParallelFor(loop_info);
1136}
1137
1138/*---------------------------------------------------------------------------*/
1139/*---------------------------------------------------------------------------*/
1140
1147template <int RankValue> void TBBTaskImplementation::
1150 const ForLoopRunInfo& run_info)
1151{
1152 ParallelLoopOptions options;
1153 if (run_info.options().has_value())
1154 options = run_info.options().value();
1155
1156 ScopedExecInfo sei(run_info);
1157 ForLoopOneExecStat* stat_info = sei.statInfo();
1158 ::Arcane::Impl::ScopedStatLoop scoped_loop(sei.isOwn() ? stat_info : nullptr);
1159
1160 if (TaskFactory::verboseLevel() >= 1) {
1161 std::cout << "TBB: TBBTaskImplementation executeMDParallelFor nb_dim=" << RankValue
1162 << " nb_element=" << loop_ranges.nbElement()
1163 << " grain_size=" << options.grainSize()
1164 << " name=" << run_info.traceInfo().traceInfo()
1165 << " has_stat_info=" << (stat_info != nullptr)
1166 << '\n';
1167 }
1168
1169 Integer max_thread = options.maxThread();
1170 // In sequential execution, call the method \a f directly.
1171 if (max_thread == 1 || max_thread == 0) {
1172 functor->executeFunctor(loop_ranges);
1173 return;
1174 }
1175
1176 // Replace the uninitialized values of \a options with those of \a m_default_loop_options
1177 ParallelLoopOptions true_options(options);
1179
1180 Integer nb_allowed_thread = m_p->nbAllowedThread();
1181 if (max_thread < 0)
1182 max_thread = nb_allowed_thread;
1183 tbb::task_arena* used_arena = nullptr;
1184 if (max_thread < nb_allowed_thread)
1185 used_arena = m_p->m_sub_arena_list[max_thread];
1186 if (!used_arena)
1187 used_arena = &(m_p->m_main_arena);
1188
1189 // For now for dimension 1, use the historical 'ParallelForExecute'
1190 if constexpr (RankValue == 1) {
1191 auto range_1d = _toTBBRange(loop_ranges);
1192 auto x1 = [&](Integer begin, Integer size) {
1193 functor->executeFunctor(makeLoopRanges(ForLoopRange(begin, size)));
1194 //functor->executeFunctor(ComplexForLoopRanges<1>(begin,size));
1195 };
1196 LambdaRangeFunctorT<decltype(x1)> functor_1d(x1);
1197 Integer begin1 = CheckedConvert::toInteger(range_1d.dim(0).begin());
1198 Integer size1 = CheckedConvert::toInteger(range_1d.dim(0).size());
1199 ParallelForExecute pfe(this, true_options, begin1, size1, &functor_1d, stat_info);
1200 used_arena->execute(pfe);
1201 }
1202 else {
1203 MDParallelForExecute<RankValue> pfe(this, true_options, loop_ranges, functor, stat_info);
1204 used_arena->execute(pfe);
1205 }
1206}
1207
1208/*---------------------------------------------------------------------------*/
1209/*---------------------------------------------------------------------------*/
1210
1212executeParallelFor(Integer begin, Integer size, Integer grain_size, IRangeFunctor* f)
1213{
1215 opts.setGrainSize(grain_size);
1216 ForLoopRunInfo run_info(opts);
1217 executeParallelFor(ParallelFor1DLoopInfo(begin, size, f, run_info));
1218}
1219
1220/*---------------------------------------------------------------------------*/
1221/*---------------------------------------------------------------------------*/
1222
1228
1229/*---------------------------------------------------------------------------*/
1230/*---------------------------------------------------------------------------*/
1231
1232TBBTaskImplementation::TaskThreadInfo* TBBTaskImplementation::
1234{
1235 Int32 thread_id = currentTaskThreadIndex();
1236 if (thread_id >= 0)
1237 return m_p->threadTaskInfo(thread_id);
1238 return nullptr;
1239}
1240
1241/*---------------------------------------------------------------------------*/
1242/*---------------------------------------------------------------------------*/
1243
1245currentTaskIndex() const
1246{
1247 Int32 thread_id = currentTaskThreadIndex();
1248 // This test was added to bypass a bug in one of the versions
1249 // of OneTBB. It is probably useless today (2025)
1250 if (thread_id < 0 || thread_id >= m_p->nbAllowedThread())
1251 return 0;
1252 TBBTaskImplementation::TaskThreadInfo* tti = currentTaskThreadInfo();
1253 if (tti) {
1254 Int32 task_index = tti->taskIndex();
1255 if (task_index >= 0)
1256 return task_index;
1257 }
1258 return thread_id;
1259}
1260
1261/*---------------------------------------------------------------------------*/
1262/*---------------------------------------------------------------------------*/
1263
1266{
1267 tbb::task_group task_group;
1268 task_group.run(taskFunctor());
1269 task_group.wait();
1270 delete this;
1271}
1272
1273/*---------------------------------------------------------------------------*/
1274/*---------------------------------------------------------------------------*/
1275
1278{
1279 tbb::task_group task_group;
1280 Integer n = tasks.size();
1281 if (n == 0)
1282 return;
1283
1284 //set_ref_count(n+1);
1285 for (Integer i = 0; i < n; ++i) {
1286 auto* t = static_cast<OneTBBTask*>(tasks[i]);
1287 task_group.run(t->taskFunctor());
1288 }
1289 task_group.wait();
1290 for (Integer i = 0; i < n; ++i) {
1291 auto* t = static_cast<OneTBBTask*>(tasks[i]);
1292 delete t;
1293 }
1294}
1295
1296/*---------------------------------------------------------------------------*/
1297/*---------------------------------------------------------------------------*/
1298
1299ITask* OneTBBTask::
1300_createChildTask(ITaskFunctor* functor)
1301{
1302 auto* t = new OneTBBTask(functor);
1303 return t;
1304}
1305
1306/*---------------------------------------------------------------------------*/
1307/*---------------------------------------------------------------------------*/
1308
1309ARCANE_DI_REGISTER_PROVIDER(TBBTaskImplementation,
1310 DependencyInjection::ProviderProperty("TBBTaskImplementation"),
1311 ARCANE_DI_INTERFACES(ITaskImplementation),
1312 ARCANE_DI_EMPTY_CONSTRUCTOR());
1313
1314/*---------------------------------------------------------------------------*/
1315/*---------------------------------------------------------------------------*/
1316
1317} // End namespace Arcane
1318
1319/*---------------------------------------------------------------------------*/
1320/*---------------------------------------------------------------------------*/
#define ARCCORE_FATAL(...)
Macro throwing a FatalErrorException.
#define ARCCORE_THROW(exception_class,...)
Macro to throw an exception with formatting.
#define ARCCORE_CHECK_POINTER(ptr)
Macro that returns the pointer ptr if it is not null or throws an exception if it is null.
Represents the bounds of a multidimensional array.
static void _setMaxAllowedThread(Int32 v)
Sets the maximum number of threads to use.
Constant view of an array of type T.
constexpr Integer size() const noexcept
Number of elements in the array.
Class to manage the profiling of a single loop execution.
Loop execution information.
Interface of a functor on a multi-dimensional iteration interval of dimension RankValue.
Interface of a functor on an iteration interval.
virtual void executeFunctor(Int32 begin, Int32 size)=0
Executes the associated method.
Interface for a task functor.
Definition Task.h:76
virtual void executeFunctor(const TaskContext &tc)=0
Executes the associated method.
Implementation of a task factory.
Int32 nbAllowedThread() const
Maximum number of threads used to manage tasks.
Interface for a concurrent task.
Definition Task.h:194
Class allowing retrieval of the time spent between the constructor call and the destructor call.
Functor over an iteration interval instantiated via a lambda function.
void launchAndWait() override
Launches the task and blocks until it finishes.
Characteristics of a multi-thread 1D loop.
Definition ParallelFor.h:35
Execution options for a parallel loop in multi-threading.
Integer grainSize() const
Size of an iteration interval.
void mergeUnsetValues(const ParallelLoopOptions &po)
Merges the unmodified values of the instance with those of po.
Int32 maxThread() const
Maximum number of allowed threads.
void setGrainSize(Integer v)
Sets the size (approximate) of an iteration interval.
void setMaxThread(Integer v)
Sets the maximum number of allowed threads.
static Arcane::Impl::ForLoopStatInfoList * _threadLocalForLoopInstance()
Definition Profiling.cc:273
static bool hasProfiling()
Indicates if profiling is active.
Deterministic implementation of ParallelFor.
void operator()(tbb::blocked_range< Integer > &range) const
Operator for a given thread.
Executor for a multi-dimensional loop.
Executor for a 1D loop.
std::vector< tbb::task_arena * > m_sub_arena_list
Array whose i-th element contains the tbb::task_arena for i thread.
Class for positioning TaskThreadInfo::taskIndex().
Int32 currentTaskThreadIndex() const final
Implementation of TaskFactory::currentTaskThreadIndex().
void initialize(Int32 nb_thread) override
void executeParallelFor(const ComplexForLoopRanges< 1 > &loop_ranges, const ForLoopRunInfo &run_info, IMDRangeFunctor< 1 > *functor) final
Executes a 1D loop in parallel.
void executeParallelFor(Int32 begin, Int32 size, IRangeFunctor *f) final
Executes the functor f in parallel.
ITask * createRootTask(ITaskFunctor *f) override
Creates a root task. The implementation must copy the value of f, which is either a TaskFunctor or a ...
void printInfos(std::ostream &o) const final
Prints information about the runtime used.
void executeParallelFor(Int32 begin, Int32 size, const ParallelLoopOptions &options, IRangeFunctor *f) final
Executes the functor f in parallel.
void executeParallelFor(const ComplexForLoopRanges< 3 > &loop_ranges, const ForLoopRunInfo &run_info, IMDRangeFunctor< 3 > *functor) final
Executes a 3D loop in parallel.
void executeParallelFor(const ComplexForLoopRanges< 4 > &loop_ranges, const ForLoopRunInfo &run_info, IMDRangeFunctor< 4 > *functor) final
Executes a 4D loop in parallel.
TaskThreadInfo * currentTaskThreadInfo() const
Instance of TaskThreadInfo associated with the current thread.
bool isActive() const final
Indicates if the implementation is active.
void _executeMDParallelFor(const ComplexForLoopRanges< RankValue > &loop_ranges, IMDRangeFunctor< RankValue > *functor, const ForLoopRunInfo &run_info)
Execution of an N-dimensional loop.
Int32 currentTaskIndex() const final
Implementation of TaskFactory::currentTaskIndex().
void executeParallelFor(const ComplexForLoopRanges< 2 > &loop_ranges, const ForLoopRunInfo &run_info, IMDRangeFunctor< 2 > *functor) final
Executes a 2D loop in parallel.
Execution context of a task.
Definition Task.h:50
static void notifyThreadCreated()
Notifies all observers of thread creation.
static const ParallelLoopOptions & defaultParallelLoopOptions()
Default parallel loop execution options.
static Integer verboseLevel()
Verbosity level.
static void setDefaultParallelLoopOptions(const ParallelLoopOptions &v)
Sets the default parallel loop execution options.
static Int32 currentTaskThreadIndex()
Index (between 0 and nbAllowedThread()-1) of the thread executing the current task.
String getStackTrace()
Returns a string containing the call stack.
-- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature --
Int32 Integer
Type representing an integer.
SimpleForLoopRanges< 1 > makeLoopRanges(Int32 n1)
Creates an iteration range [0,n1[, [0,n2[.
std::int32_t Int32
Signed integer type of 32 bits.