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 }; 95 96 class alignas(max_nfs_size) stack_size_control : public control_storage { 97 std::size_t default_value() const override { 98 #if _WIN32_WINNT >= 0x0602 /* _WIN32_WINNT_WIN8 */ 99 static auto ThreadStackSizeDefault = [] { 100 ULONG_PTR hi, lo; 101 GetCurrentThreadStackLimits(&lo, &hi); 102 return hi - lo; 103 }(); 104 return ThreadStackSizeDefault; 105 #else 106 return ThreadStackSize; 107 #endif 108 } 109 void apply_active(std::size_t new_active) override { 110 control_storage::apply_active(new_active); 111 #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) 112 __TBB_ASSERT( false, "For Windows 8 Store* apps we must not set stack size" ); 113 #endif 114 } 115 }; 116 117 class alignas(max_nfs_size) terminate_on_exception_control : public control_storage { 118 std::size_t default_value() const override { 119 return 0; 120 } 121 }; 122 123 class alignas(max_nfs_size) lifetime_control : public control_storage { 124 bool is_first_arg_preferred(std::size_t, std::size_t) const override { 125 return false; // not interested 126 } 127 std::size_t default_value() const override { 128 return 0; 129 } 130 void apply_active(std::size_t new_active) override { 131 if (new_active == 1) { 132 // reserve the market reference 133 threading_control::register_lifetime_control(); 134 } else if (new_active == 0) { // new_active == 0 135 threading_control::unregister_lifetime_control(/*blocking_terminate*/ false); 136 } 137 control_storage::apply_active(new_active); 138 } 139 }; 140 141 static allowed_parallelism_control allowed_parallelism_ctl; 142 static stack_size_control stack_size_ctl; 143 static terminate_on_exception_control terminate_on_exception_ctl; 144 static lifetime_control lifetime_ctl; 145 static control_storage *controls[] = {&allowed_parallelism_ctl, &stack_size_ctl, &terminate_on_exception_ctl, &lifetime_ctl}; 146 147 void global_control_lock() { 148 for (auto& ctl : controls) { 149 ctl->my_list_mutex.lock(); 150 } 151 } 152 153 void global_control_unlock() { 154 int N = std::distance(std::begin(controls), std::end(controls)); 155 for (int i = N - 1; i >= 0; --i) { 156 controls[i]->my_list_mutex.unlock(); 157 } 158 } 159 160 std::size_t global_control_active_value_unsafe(d1::global_control::parameter param) { 161 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr); 162 return controls[param]->active_value_unsafe(); 163 } 164 165 //! Comparator for a set of global_control objects 166 inline bool control_storage_comparator::operator()(const d1::global_control* lhs, const d1::global_control* rhs) const { 167 __TBB_ASSERT_RELEASE(lhs->my_param < d1::global_control::parameter_max , nullptr); 168 return lhs->my_value < rhs->my_value || (lhs->my_value == rhs->my_value && lhs < rhs); 169 } 170 171 bool terminate_on_exception() { 172 return d1::global_control::active_value(d1::global_control::terminate_on_exception) == 1; 173 } 174 175 struct global_control_impl { 176 private: 177 static bool erase_if_present(control_storage* const c, d1::global_control& gc) { 178 auto it = c->my_list.find(&gc); 179 if (it != c->my_list.end()) { 180 c->my_list.erase(it); 181 return true; 182 } 183 return false; 184 } 185 186 public: 187 188 static void create(d1::global_control& gc) { 189 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 190 control_storage* const c = controls[gc.my_param]; 191 192 spin_mutex::scoped_lock lock(c->my_list_mutex); 193 if (c->my_list.empty() || c->is_first_arg_preferred(gc.my_value, c->my_active_value)) { 194 // to guarantee that apply_active() is called with current active value, 195 // calls it here and in internal_destroy() under my_list_mutex 196 c->apply_active(gc.my_value); 197 } 198 c->my_list.insert(&gc); 199 } 200 201 static void destroy(d1::global_control& gc) { 202 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 203 control_storage* const c = controls[gc.my_param]; 204 // Concurrent reading and changing global parameter is possible. 205 spin_mutex::scoped_lock lock(c->my_list_mutex); 206 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle || !c->my_list.empty(), nullptr); 207 std::size_t new_active = (std::size_t)(-1), old_active = c->my_active_value; 208 209 if (!erase_if_present(c, gc)) { 210 __TBB_ASSERT(gc.my_param == d1::global_control::scheduler_handle , nullptr); 211 return; 212 } 213 if (c->my_list.empty()) { 214 __TBB_ASSERT(new_active == (std::size_t) - 1, nullptr); 215 new_active = c->default_value(); 216 } else { 217 new_active = (*c->my_list.begin())->my_value; 218 } 219 if (new_active != old_active) { 220 c->apply_active(new_active); 221 } 222 } 223 224 static bool remove_and_check_if_empty(d1::global_control& gc) { 225 __TBB_ASSERT_RELEASE(gc.my_param < d1::global_control::parameter_max, nullptr); 226 control_storage* const c = controls[gc.my_param]; 227 228 spin_mutex::scoped_lock lock(c->my_list_mutex); 229 __TBB_ASSERT(!c->my_list.empty(), nullptr); 230 erase_if_present(c, gc); 231 return c->my_list.empty(); 232 } 233 #if TBB_USE_ASSERT 234 static bool is_present(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 auto it = c->my_list.find(&gc); 240 if (it != c->my_list.end()) { 241 return true; 242 } 243 return false; 244 } 245 #endif // TBB_USE_ASSERT 246 }; 247 248 void __TBB_EXPORTED_FUNC create(d1::global_control& gc) { 249 global_control_impl::create(gc); 250 } 251 void __TBB_EXPORTED_FUNC destroy(d1::global_control& gc) { 252 global_control_impl::destroy(gc); 253 } 254 255 bool remove_and_check_if_empty(d1::global_control& gc) { 256 return global_control_impl::remove_and_check_if_empty(gc); 257 } 258 #if TBB_USE_ASSERT 259 bool is_present(d1::global_control& gc) { 260 return global_control_impl::is_present(gc); 261 } 262 #endif // TBB_USE_ASSERT 263 std::size_t __TBB_EXPORTED_FUNC global_control_active_value(int param) { 264 __TBB_ASSERT_RELEASE(param < d1::global_control::parameter_max, nullptr); 265 return controls[param]->active_value(); 266 } 267 268 } // namespace r1 269 } // namespace detail 270 } // namespace tbb 271