1 /* 2 Copyright (c) 2005-2023 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 #include "oneapi/tbb/detail/_config.h" 18 #include "oneapi/tbb/detail/_template_helpers.h" 19 20 #include "oneapi/tbb/global_control.h" 21 #include "oneapi/tbb/tbb_allocator.h" 22 #include "oneapi/tbb/spin_mutex.h" 23 24 #include "governor.h" 25 #include "threading_control.h" 26 #include "market.h" 27 #include "misc.h" 28 29 #include <atomic> 30 #include <set> 31 32 namespace tbb { 33 namespace detail { 34 namespace r1 { 35 36 //! Comparator for a set of global_control objects 37 struct control_storage_comparator { 38 bool operator()(const d1::global_control* lhs, const d1::global_control* rhs) const; 39 }; 40 41 class control_storage { 42 friend struct global_control_impl; 43 friend std::size_t global_control_active_value(int); 44 friend void global_control_lock(); 45 friend void global_control_unlock(); 46 friend std::size_t global_control_active_value_unsafe(d1::global_control::parameter); 47 protected: 48 std::size_t my_active_value{0}; 49 std::set<d1::global_control*, control_storage_comparator, tbb_allocator<d1::global_control*>> my_list{}; 50 spin_mutex my_list_mutex{}; 51 public: 52 virtual std::size_t default_value() const = 0; 53 virtual void apply_active(std::size_t new_active) { 54 my_active_value = new_active; 55 } 56 virtual bool is_first_arg_preferred(std::size_t a, std::size_t b) const { 57 return a>b; // prefer max by default 58 } 59 virtual std::size_t active_value() { 60 spin_mutex::scoped_lock lock(my_list_mutex); // protect my_list.empty() call 61 return !my_list.empty() ? my_active_value : default_value(); 62 } 63 64 std::size_t active_value_unsafe() { 65 return !my_list.empty() ? my_active_value : default_value(); 66 } 67 }; 68 69 class alignas(max_nfs_size) allowed_parallelism_control : public control_storage { 70 std::size_t default_value() const override { 71 return max(1U, governor::default_num_threads()); 72 } 73 bool is_first_arg_preferred(std::size_t a, std::size_t b) const override { 74 return a<b; // prefer min allowed parallelism 75 } 76 void apply_active(std::size_t new_active) override { 77 control_storage::apply_active(new_active); 78 __TBB_ASSERT(my_active_value >= 1, nullptr); 79 // -1 to take external thread into account 80 threading_control::set_active_num_workers(my_active_value - 1); 81 } 82 std::size_t active_value() override { 83 spin_mutex::scoped_lock lock(my_list_mutex); // protect my_list.empty() call 84 if (my_list.empty()) { 85 return default_value(); 86 } 87 88 // non-zero, if market is active 89 const std::size_t workers = threading_control::max_num_workers(); 90 // We can't exceed market's maximal number of workers. 91 // +1 to take external thread into account 92 return workers ? min(workers + 1, my_active_value) : my_active_value; 93 } 94 public: 95 std::size_t active_value_if_present() const { 96 return !my_list.empty() ? my_active_value : 0; 97 } 98 }; 99 100 class alignas(max_nfs_size) stack_size_control : public control_storage { 101 std::size_t default_value() const override { 102 #if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ 103 static auto ThreadStackSizeDefault = [] { 104 ULONG_PTR hi, lo; 105 GetCurrentThreadStackLimits(&lo, &hi); 106 return hi - lo; 107 }(); 108 return ThreadStackSizeDefault; 109 #else 110 return ThreadStackSize; 111 #endif 112 } 113 void apply_active(std::size_t new_active) override { 114 control_storage::apply_active(new_active); 115 #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) 116 __TBB_ASSERT( false, "For Windows 8 Store* apps we must not set stack size" ); 117 #endif 118 } 119 }; 120 121 class alignas(max_nfs_size) terminate_on_exception_control : public control_storage { 122 std::size_t default_value() const override { 123 return 0; 124 } 125 }; 126 127 class alignas(max_nfs_size) lifetime_control : public control_storage { 128 bool is_first_arg_preferred(std::size_t, std::size_t) const override { 129 return false; // not interested 130 } 131 std::size_t default_value() const override { 132 return 0; 133 } 134 void apply_active(std::size_t new_active) override { 135 if (new_active == 1) { 136 // reserve the market reference 137 threading_control::register_lifetime_control(); 138 } else if (new_active == 0) { // new_active == 0 139 threading_control::unregister_lifetime_control(/*blocking_terminate*/ false); 140 } 141 control_storage::apply_active(new_active); 142 } 143 144 public: 145 bool is_empty() { 146 spin_mutex::scoped_lock lock(my_list_mutex); 147 return my_list.empty(); 148 } 149 }; 150 151 static allowed_parallelism_control allowed_parallelism_ctl; 152 static stack_size_control stack_size_ctl; 153 static terminate_on_exception_control terminate_on_exception_ctl; 154 static lifetime_control lifetime_ctl; 155 static control_storage *controls[] = {&allowed_parallelism_ctl, &stack_size_ctl, &terminate_on_exception_ctl, &lifetime_ctl}; 156 157 void global_control_lock() { 158 for (auto& ctl : controls) { 159 ctl->my_list_mutex.lock(); 160 } 161 } 162 163 void global_control_unlock() { 164 int N = std::distance(std::begin(controls), std::end(controls)); 165 for (int i = N - 1; i >= 0; --i) { 166 controls[i]->my_list_mutex.unlock(); 167 } 168 } 169 170 std::size_t global_control_active_value_unsafe(d1::global_control::parameter param) { 171 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr); 172 return controls[param]->active_value_unsafe(); 173 } 174 175 //! Comparator for a set of global_control objects 176 inline bool control_storage_comparator::operator()(const d1::global_control* lhs, const d1::global_control* rhs) const { 177 __TBB_ASSERT_RELEASE(lhs->my_param < d1::global_control::parameter_max , nullptr); 178 return lhs->my_value < rhs->my_value || (lhs->my_value == rhs->my_value && lhs < rhs); 179 } 180 181 bool terminate_on_exception() { 182 return d1::global_control::active_value(d1::global_control::terminate_on_exception) == 1; 183 } 184 185 struct global_control_impl { 186 private: 187 static bool erase_if_present(control_storage* const c, d1::global_control& gc) { 188 auto it = c->my_list.find(&gc); 189 if (it != c->my_list.end()) { 190 c->my_list.erase(it); 191 return true; 192 } 193 return false; 194 } 195 196 public: 197 198 static void create(d1::global_control& gc) { 199 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 200 control_storage* const c = controls[gc.my_param]; 201 202 spin_mutex::scoped_lock lock(c->my_list_mutex); 203 if (c->my_list.empty() || c->is_first_arg_preferred(gc.my_value, c->my_active_value)) { 204 // to guarantee that apply_active() is called with current active value, 205 // calls it here and in internal_destroy() under my_list_mutex 206 c->apply_active(gc.my_value); 207 } 208 c->my_list.insert(&gc); 209 } 210 211 static void destroy(d1::global_control& gc) { 212 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 213 control_storage* const c = controls[gc.my_param]; 214 // Concurrent reading and changing global parameter is possible. 215 spin_mutex::scoped_lock lock(c->my_list_mutex); 216 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle || !c->my_list.empty(), nullptr); 217 std::size_t new_active = (std::size_t)(-1), old_active = c->my_active_value; 218 219 if (!erase_if_present(c, gc)) { 220 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle , nullptr); 221 return; 222 } 223 if (c->my_list.empty()) { 224 __TBB_ASSERT(new_active == (std::size_t) - 1, nullptr); 225 new_active = c->default_value(); 226 } else { 227 new_active = (*c->my_list.begin())->my_value; 228 } 229 if (new_active != old_active) { 230 c->apply_active(new_active); 231 } 232 } 233 234 static bool remove_and_check_if_empty(d1::global_control& gc) { 235 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 236 control_storage* const c = controls[gc.my_param]; 237 238 spin_mutex::scoped_lock lock(c->my_list_mutex); 239 __TBB_ASSERT(!c->my_list.empty(), nullptr); 240 erase_if_present(c, gc); 241 return c->my_list.empty(); 242 } 243 #if TBB_USE_ASSERT 244 static bool is_present(d1::global_control& gc) { 245 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 246 control_storage* const c = controls[gc.my_param]; 247 248 spin_mutex::scoped_lock lock(c->my_list_mutex); 249 auto it = c->my_list.find(&gc); 250 if (it != c->my_list.end()) { 251 return true; 252 } 253 return false; 254 } 255 #endif // TBB_USE_ASSERT 256 }; 257 258 void __TBB_EXPORTED_FUNC create(d1::global_control& gc) { 259 global_control_impl::create(gc); 260 } 261 void __TBB_EXPORTED_FUNC destroy(d1::global_control& gc) { 262 global_control_impl::destroy(gc); 263 } 264 265 bool remove_and_check_if_empty(d1::global_control& gc) { 266 return global_control_impl::remove_and_check_if_empty(gc); 267 } 268 #if TBB_USE_ASSERT 269 bool is_present(d1::global_control& gc) { 270 return global_control_impl::is_present(gc); 271 } 272 #endif // TBB_USE_ASSERT 273 std::size_t __TBB_EXPORTED_FUNC global_control_active_value(int param) { 274 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr); 275 return controls[param]->active_value(); 276 } 277 278 } // namespace r1 279 } // namespace detail 280 } // namespace tbb 281