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