xref: /oneTBB/src/tbb/global_control.cpp (revision 89b2e0e3)
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