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