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