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