xref: /oneTBB/src/tbb/global_control.cpp (revision 57f524ca)
1 /*
2     Copyright (c) 2005-2021 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