xref: /oneTBB/include/oneapi/tbb/task_arena.h (revision 23266bc5)
1 /*
2     Copyright (c) 2005-2022 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 #ifndef __TBB_task_arena_H
18 #define __TBB_task_arena_H
19 
20 #include "detail/_config.h"
21 
22 #include "detail/_aligned_space.h"
23 #include "detail/_attach.h"
24 #include "detail/_exception.h"
25 #include "detail/_namespace_injection.h"
26 #include "detail/_small_object_pool.h"
27 #include "detail/_task.h"
28 
29 #include "detail/_task_handle.h"
30 
31 #if __TBB_ARENA_BINDING
32 #include "info.h"
33 #endif /*__TBB_ARENA_BINDING*/
34 
35 namespace tbb {
36 namespace detail {
37 
38 namespace d1 {
39 
40 template<typename F, typename R>
41 class task_arena_function : public delegate_base {
42     F &my_func;
43     aligned_space<R> my_return_storage;
44     bool my_constructed{false};
45     // The function should be called only once.
46     bool operator()() const override {
47         new (my_return_storage.begin()) R(my_func());
48         return true;
49     }
50 public:
51     task_arena_function(F& f) : my_func(f) {}
52     // The function can be called only after operator() and only once.
53     R consume_result() {
54         my_constructed = true;
55         return std::move(*(my_return_storage.begin()));
56     }
57     ~task_arena_function() override {
58         if (my_constructed) {
59             my_return_storage.begin()->~R();
60         }
61     }
62 };
63 
64 template<typename F>
65 class task_arena_function<F,void> : public delegate_base {
66     F &my_func;
67     bool operator()() const override {
68         my_func();
69         return true;
70     }
71 public:
72     task_arena_function(F& f) : my_func(f) {}
73     void consume_result() const {}
74 
75     friend class task_arena_base;
76 };
77 
78 class task_arena_base;
79 class task_scheduler_observer;
80 } // namespace d1
81 
82 namespace r1 {
83 class arena;
84 struct task_arena_impl;
85 
86 TBB_EXPORT void __TBB_EXPORTED_FUNC observe(d1::task_scheduler_observer&, bool);
87 TBB_EXPORT void __TBB_EXPORTED_FUNC initialize(d1::task_arena_base&);
88 TBB_EXPORT void __TBB_EXPORTED_FUNC terminate(d1::task_arena_base&);
89 TBB_EXPORT bool __TBB_EXPORTED_FUNC attach(d1::task_arena_base&);
90 TBB_EXPORT void __TBB_EXPORTED_FUNC execute(d1::task_arena_base&, d1::delegate_base&);
91 TBB_EXPORT void __TBB_EXPORTED_FUNC wait(d1::task_arena_base&);
92 TBB_EXPORT int  __TBB_EXPORTED_FUNC max_concurrency(const d1::task_arena_base*);
93 TBB_EXPORT void __TBB_EXPORTED_FUNC isolate_within_arena(d1::delegate_base& d, std::intptr_t);
94 
95 TBB_EXPORT void __TBB_EXPORTED_FUNC enqueue(d1::task&, d1::task_arena_base*);
96 TBB_EXPORT void __TBB_EXPORTED_FUNC enqueue(d1::task&, d1::task_group_context&, d1::task_arena_base*);
97 TBB_EXPORT void __TBB_EXPORTED_FUNC submit(d1::task&, d1::task_group_context&, arena*, std::uintptr_t);
98 } // namespace r1
99 
100 namespace d2 {
101 inline void enqueue_impl(task_handle&& th, d1::task_arena_base* ta) {
102     __TBB_ASSERT(th != nullptr, "Attempt to schedule empty task_handle");
103 
104     auto& ctx = task_handle_accessor::ctx_of(th);
105 
106     // Do not access th after release
107     r1::enqueue(*task_handle_accessor::release(th), ctx, ta);
108 }
109 } //namespace d2
110 
111 namespace d1 {
112 
113 static constexpr int priority_stride = INT_MAX / 4;
114 
115 class task_arena_base {
116     friend struct r1::task_arena_impl;
117     friend void r1::observe(d1::task_scheduler_observer&, bool);
118 public:
119     enum class priority : int {
120         low    = 1 * priority_stride,
121         normal = 2 * priority_stride,
122         high   = 3 * priority_stride
123     };
124 #if __TBB_ARENA_BINDING
125     using constraints = tbb::detail::d1::constraints;
126 #endif /*__TBB_ARENA_BINDING*/
127 protected:
128     //! Special settings
129     intptr_t my_version_and_traits;
130 
131     std::atomic<do_once_state> my_initialization_state;
132 
133     //! nullptr if not currently initialized.
134     std::atomic<r1::arena*> my_arena;
135     static_assert(sizeof(std::atomic<r1::arena*>) == sizeof(r1::arena*),
136         "To preserve backward compatibility we need the equal size of an atomic pointer and a pointer");
137 
138     //! Concurrency level for deferred initialization
139     int my_max_concurrency;
140 
141     //! Reserved slots for external threads
142     unsigned my_num_reserved_slots;
143 
144     //! Arena priority
145     priority my_priority;
146 
147     //! The NUMA node index to which the arena will be attached
148     numa_node_id my_numa_id;
149 
150     //! The core type index to which arena will be attached
151     core_type_id my_core_type;
152 
153     //! Number of threads per core
154     int my_max_threads_per_core;
155 
156     // Backward compatibility checks.
157     core_type_id core_type() const {
158         return (my_version_and_traits & core_type_support_flag) == core_type_support_flag ? my_core_type : automatic;
159     }
160     int max_threads_per_core() const {
161         return (my_version_and_traits & core_type_support_flag) == core_type_support_flag ? my_max_threads_per_core : automatic;
162     }
163 
164     enum {
165         default_flags = 0
166         , core_type_support_flag = 1
167     };
168 
169     task_arena_base(int max_concurrency, unsigned reserved_for_masters, priority a_priority)
170         : my_version_and_traits(default_flags | core_type_support_flag)
171         , my_initialization_state(do_once_state::uninitialized)
172         , my_arena(nullptr)
173         , my_max_concurrency(max_concurrency)
174         , my_num_reserved_slots(reserved_for_masters)
175         , my_priority(a_priority)
176         , my_numa_id(automatic)
177         , my_core_type(automatic)
178         , my_max_threads_per_core(automatic)
179         {}
180 
181 #if __TBB_ARENA_BINDING
182     task_arena_base(const constraints& constraints_, unsigned reserved_for_masters, priority a_priority)
183         : my_version_and_traits(default_flags | core_type_support_flag)
184         , my_initialization_state(do_once_state::uninitialized)
185         , my_arena(nullptr)
186         , my_max_concurrency(constraints_.max_concurrency)
187         , my_num_reserved_slots(reserved_for_masters)
188         , my_priority(a_priority)
189         , my_numa_id(constraints_.numa_id)
190         , my_core_type(constraints_.core_type)
191         , my_max_threads_per_core(constraints_.max_threads_per_core)
192         {}
193 #endif /*__TBB_ARENA_BINDING*/
194 public:
195     //! Typedef for number of threads that is automatic.
196     static const int automatic = -1;
197     static const int not_initialized = -2;
198 };
199 
200 template<typename R, typename F>
201 R isolate_impl(F& f) {
202     task_arena_function<F, R> func(f);
203     r1::isolate_within_arena(func, /*isolation*/ 0);
204     return func.consume_result();
205 }
206 
207 template <typename F>
208 class enqueue_task : public task {
209     small_object_allocator m_allocator;
210     const F m_func;
211 
212     void finalize(const execution_data& ed) {
213         m_allocator.delete_object(this, ed);
214     }
215     task* execute(execution_data& ed) override {
216         m_func();
217         finalize(ed);
218         return nullptr;
219     }
220     task* cancel(execution_data&) override {
221         __TBB_ASSERT_RELEASE(false, "Unhandled exception from enqueue task is caught");
222         return nullptr;
223     }
224 public:
225     enqueue_task(const F& f, small_object_allocator& alloc) : m_allocator(alloc), m_func(f) {}
226     enqueue_task(F&& f, small_object_allocator& alloc) : m_allocator(alloc), m_func(std::move(f)) {}
227 };
228 
229 template<typename F>
230 void enqueue_impl(F&& f, task_arena_base* ta) {
231     small_object_allocator alloc{};
232     r1::enqueue(*alloc.new_object<enqueue_task<typename std::decay<F>::type>>(std::forward<F>(f), alloc), ta);
233 }
234 /** 1-to-1 proxy representation class of scheduler's arena
235  * Constructors set up settings only, real construction is deferred till the first method invocation
236  * Destructor only removes one of the references to the inner arena representation.
237  * Final destruction happens when all the references (and the work) are gone.
238  */
239 class task_arena : public task_arena_base {
240 
241     void mark_initialized() {
242         __TBB_ASSERT( my_arena.load(std::memory_order_relaxed), "task_arena initialization is incomplete" );
243         my_initialization_state.store(do_once_state::initialized, std::memory_order_release);
244     }
245 
246     template<typename R, typename F>
247     R execute_impl(F& f) {
248         initialize();
249         task_arena_function<F, R> func(f);
250         r1::execute(*this, func);
251         return func.consume_result();
252     }
253 public:
254     //! Creates task_arena with certain concurrency limits
255     /** Sets up settings only, real construction is deferred till the first method invocation
256      *  @arg max_concurrency specifies total number of slots in arena where threads work
257      *  @arg reserved_for_masters specifies number of slots to be used by external threads only.
258      *       Value of 1 is default and reflects behavior of implicit arenas.
259      **/
260     task_arena(int max_concurrency_ = automatic, unsigned reserved_for_masters = 1,
261                priority a_priority = priority::normal)
262         : task_arena_base(max_concurrency_, reserved_for_masters, a_priority)
263     {}
264 
265 #if __TBB_ARENA_BINDING
266     //! Creates task arena pinned to certain NUMA node
267     task_arena(const constraints& constraints_, unsigned reserved_for_masters = 1,
268                priority a_priority = priority::normal)
269         : task_arena_base(constraints_, reserved_for_masters, a_priority)
270     {}
271 
272     //! Copies settings from another task_arena
273     task_arena(const task_arena &s) // copy settings but not the reference or instance
274         : task_arena_base(
275             constraints{}
276                 .set_numa_id(s.my_numa_id)
277                 .set_max_concurrency(s.my_max_concurrency)
278                 .set_core_type(s.my_core_type)
279                 .set_max_threads_per_core(s.my_max_threads_per_core)
280             , s.my_num_reserved_slots, s.my_priority)
281     {}
282 #else
283     //! Copies settings from another task_arena
284     task_arena(const task_arena& a) // copy settings but not the reference or instance
285         : task_arena_base(a.my_max_concurrency, a.my_num_reserved_slots, a.my_priority)
286     {}
287 #endif /*__TBB_ARENA_BINDING*/
288 
289     //! Tag class used to indicate the "attaching" constructor
290     struct attach {};
291 
292     //! Creates an instance of task_arena attached to the current arena of the thread
293     explicit task_arena( attach )
294         : task_arena_base(automatic, 1, priority::normal) // use default settings if attach fails
295     {
296         if (r1::attach(*this)) {
297             mark_initialized();
298         }
299     }
300 
301     //! Creates an instance of task_arena attached to the current arena of the thread
302     explicit task_arena(d1::attach)
303         : task_arena(attach{})
304     {}
305 
306     //! Forces allocation of the resources for the task_arena as specified in constructor arguments
307     void initialize() {
308         atomic_do_once([this]{ r1::initialize(*this); }, my_initialization_state);
309     }
310 
311     //! Overrides concurrency level and forces initialization of internal representation
312     void initialize(int max_concurrency_, unsigned reserved_for_masters = 1,
313                     priority a_priority = priority::normal)
314     {
315         __TBB_ASSERT(!my_arena.load(std::memory_order_relaxed), "Impossible to modify settings of an already initialized task_arena");
316         if( !is_active() ) {
317             my_max_concurrency = max_concurrency_;
318             my_num_reserved_slots = reserved_for_masters;
319             my_priority = a_priority;
320             r1::initialize(*this);
321             mark_initialized();
322         }
323     }
324 
325 #if __TBB_ARENA_BINDING
326     void initialize(constraints constraints_, unsigned reserved_for_masters = 1,
327                     priority a_priority = priority::normal)
328     {
329         __TBB_ASSERT(!my_arena.load(std::memory_order_relaxed), "Impossible to modify settings of an already initialized task_arena");
330         if( !is_active() ) {
331             my_numa_id = constraints_.numa_id;
332             my_max_concurrency = constraints_.max_concurrency;
333             my_core_type = constraints_.core_type;
334             my_max_threads_per_core = constraints_.max_threads_per_core;
335             my_num_reserved_slots = reserved_for_masters;
336             my_priority = a_priority;
337             r1::initialize(*this);
338             mark_initialized();
339         }
340     }
341 #endif /*__TBB_ARENA_BINDING*/
342 
343     //! Attaches this instance to the current arena of the thread
344     void initialize(attach) {
345         // TODO: decide if this call must be thread-safe
346         __TBB_ASSERT(!my_arena.load(std::memory_order_relaxed), "Impossible to modify settings of an already initialized task_arena");
347         if( !is_active() ) {
348             if ( !r1::attach(*this) ) {
349                 r1::initialize(*this);
350             }
351             mark_initialized();
352         }
353     }
354 
355     //! Attaches this instance to the current arena of the thread
356     void initialize(d1::attach) {
357         initialize(attach{});
358     }
359 
360     //! Removes the reference to the internal arena representation.
361     //! Not thread safe wrt concurrent invocations of other methods.
362     void terminate() {
363         if( is_active() ) {
364             r1::terminate(*this);
365             my_initialization_state.store(do_once_state::uninitialized, std::memory_order_relaxed);
366         }
367     }
368 
369     //! Removes the reference to the internal arena representation, and destroys the external object.
370     //! Not thread safe wrt concurrent invocations of other methods.
371     ~task_arena() {
372         terminate();
373     }
374 
375     //! Returns true if the arena is active (initialized); false otherwise.
376     //! The name was chosen to match a task_scheduler_init method with the same semantics.
377     bool is_active() const {
378         return my_initialization_state.load(std::memory_order_acquire) == do_once_state::initialized;
379     }
380 
381     //! Enqueues a task into the arena to process a functor, and immediately returns.
382     //! Does not require the calling thread to join the arena
383 
384     template<typename F>
385     void enqueue(F&& f) {
386         initialize();
387         enqueue_impl(std::forward<F>(f), this);
388     }
389 
390     //! Enqueues a task into the arena to process a functor wrapped in task_handle, and immediately returns.
391     //! Does not require the calling thread to join the arena
392     void enqueue(d2::task_handle&& th) {
393         initialize();
394         d2::enqueue_impl(std::move(th), this);
395     }
396 
397     //! Joins the arena and executes a mutable functor, then returns
398     //! If not possible to join, wraps the functor into a task, enqueues it and waits for task completion
399     //! Can decrement the arena demand for workers, causing a worker to leave and free a slot to the calling thread
400     //! Since C++11, the method returns the value returned by functor (prior to C++11 it returns void).
401     template<typename F>
402     auto execute(F&& f) -> decltype(f()) {
403         return execute_impl<decltype(f())>(f);
404     }
405 
406 #if __TBB_EXTRA_DEBUG
407     //! Returns my_num_reserved_slots
408     int debug_reserved_slots() const {
409         // Handle special cases inside the library
410         return my_num_reserved_slots;
411     }
412 
413     //! Returns my_max_concurrency
414     int debug_max_concurrency() const {
415         // Handle special cases inside the library
416         return my_max_concurrency;
417     }
418 
419     //! Wait for all work in the arena to be completed
420     //! Even submitted by other application threads
421     //! Joins arena if/when possible (in the same way as execute())
422     void debug_wait_until_empty() {
423         initialize();
424         r1::wait(*this);
425     }
426 #endif //__TBB_EXTRA_DEBUG
427 
428     //! Returns the maximal number of threads that can work inside the arena
429     int max_concurrency() const {
430         // Handle special cases inside the library
431         return (my_max_concurrency > 1) ? my_max_concurrency : r1::max_concurrency(this);
432     }
433 
434     friend void submit(task& t, task_arena& ta, task_group_context& ctx, bool as_critical) {
435         __TBB_ASSERT(ta.is_active(), nullptr);
436         call_itt_task_notify(releasing, &t);
437         r1::submit(t, ctx, ta.my_arena.load(std::memory_order_relaxed), as_critical ? 1 : 0);
438     }
439 };
440 
441 //! Executes a mutable functor in isolation within the current task arena.
442 //! Since C++11, the method returns the value returned by functor (prior to C++11 it returns void).
443 template<typename F>
444 inline auto isolate(F&& f) -> decltype(f()) {
445     return isolate_impl<decltype(f())>(f);
446 }
447 
448 //! Returns the index, aka slot number, of the calling thread in its current arena
449 inline int current_thread_index() {
450     slot_id idx = r1::execution_slot(nullptr);
451     return idx == slot_id(-1) ? task_arena_base::not_initialized : int(idx);
452 }
453 
454 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
455 inline bool is_inside_task() {
456     return nullptr != current_context();
457 }
458 #endif //__TBB_PREVIEW_TASK_GROUP_EXTENSIONS
459 
460 //! Returns the maximal number of threads that can work inside the arena
461 inline int max_concurrency() {
462     return r1::max_concurrency(nullptr);
463 }
464 
465 inline void enqueue(d2::task_handle&& th) {
466     d2::enqueue_impl(std::move(th), nullptr);
467 }
468 
469 template<typename F>
470 inline void enqueue(F&& f) {
471     enqueue_impl(std::forward<F>(f), nullptr);
472 }
473 
474 using r1::submit;
475 
476 } // namespace d1
477 } // namespace detail
478 
479 inline namespace v1 {
480 using detail::d1::task_arena;
481 using detail::d1::attach;
482 
483 #if __TBB_PREVIEW_TASK_GROUP_EXTENSIONS
484 using detail::d1::is_inside_task;
485 #endif
486 
487 namespace this_task_arena {
488 using detail::d1::current_thread_index;
489 using detail::d1::max_concurrency;
490 using detail::d1::isolate;
491 
492 using detail::d1::enqueue;
493 } // namespace this_task_arena
494 
495 } // inline namespace v1
496 
497 } // namespace tbb
498 #endif /* __TBB_task_arena_H */
499