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