libsemigroups  v3.1.3
C++ library for semigroups and monoids
Loading...
Searching...
No Matches
race.hpp
1//
2// libsemigroups - C++ library for semigroups and monoids
3// Copyright (C) 2019-2025 James D. Mitchell
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17//
18
19// This file contains the declaration of the Race class template for
20// competitively running different functions/methods in different threads, and
21// obtaining the winner.
22
23// TODO(later):
24// 1. consider if keeping killed off methods has any uses
25// 2. update run_until to be similar to Runner::run_until
26// 3. add tpp file
27
28#ifndef LIBSEMIGROUPS_DETAIL_RACE_HPP_
29#define LIBSEMIGROUPS_DETAIL_RACE_HPP_
30
31#include <algorithm> // for min, find_if
32#include <chrono> // for nanoseconds
33#include <cstddef> // for size_t
34#include <exception> // for exception
35#include <memory> // for shared_ptr, operator!=, opera...
36#include <mutex> // for mutex, lock_guard
37#include <string_view> // for basic_string_view
38#include <thread> // for thread, get_id, __thread_id
39#include <type_traits> // for invoke_result_t, is_same_v
40#include <typeinfo> // for type_info
41#include <utility> // for move, forward
42
43#include "libsemigroups/constants.hpp" // for UNDEFINED
44#include "libsemigroups/debug.hpp" // for LIBSEMIGROUPS_ASSERT
45#include "libsemigroups/exception.hpp" // for LIBSEMIGROUPS_EXCEPTION
46#include "libsemigroups/runner.hpp" // for Runner, delta, Reporter
47
48#include "report.hpp" // for report_default, thread_id
49#include "timer.hpp" // for Timer, string_time
50
51namespace libsemigroups {
52 namespace detail {
53 class Race : public Reporter {
54 size_t _max_threads;
55 std::mutex _mtx;
56 std::vector<std::shared_ptr<Runner>> _runners;
57 std::shared_ptr<Runner> _winner;
58 size_t _winner_index;
59
60 public:
61 // Construct an empty Race object, with maximum number of threads set to
62 // std::thread::hardware_concurrency.
63 Race();
64
65 // TODO(1) to cpp
66 Race& init() {
67 _max_threads = std::thread::hardware_concurrency();
68 // do nothing to the _mtx
69 _runners.clear();
70 _winner = nullptr;
71 _winner_index = UNDEFINED;
72 return *this;
73 }
74
75 Race(Race const& other) : Race() {
76 // Can't use = default because std::mutex is non-copyable.
77 _max_threads = other._max_threads;
78 // do nothing to the _mtx
79 _runners = other._runners;
80 _winner = other._winner;
81 _winner_index = other._winner_index;
82 }
83
84 // TODO(1) to cpp
85 Race(Race&& other)
86 : _max_threads(std::move(other._max_threads)),
87 _mtx(),
88 _runners(std::move(other._runners)),
89 _winner(std::move(other._winner)),
90 _winner_index(std::move(other._winner_index)) {}
91
92 // TODO(1) to cpp
93 Race& operator=(Race const& other) {
94 // Can't use = default because std::mutex is non-copyable.
95 _max_threads = other._max_threads;
96 // do nothing to the _mtx
97 _runners = other._runners;
98 _winner = other._winner;
99 _winner_index = other._winner_index;
100 return *this;
101 }
102
103 // TODO(1) to cpp
104 Race& operator=(Race&& other) {
105 // Can't use = default because std::mutex is non-copyable.
106 _max_threads = std::move(other._max_threads);
107 // do nothing to the _mtx
108 _runners = std::move(other._runners);
109 _winner = std::move(other._winner);
110 _winner_index = std::move(other._winner_index);
111 return *this;
112 }
113
114 ~Race();
115
116 // Set the maximum number of threads, throws if try to set to 0.
117 Race& max_threads(size_t val) noexcept {
118 LIBSEMIGROUPS_ASSERT(val != 0);
119 _max_threads = val;
120 return *this;
121 }
122
123 [[nodiscard]] size_t max_threads() const noexcept {
124 return _max_threads;
125 }
126
127 // Runs the method Runner::run on every Runner in the Race, and returns
128 // the one that finishes first. The losers are deleted.
129 [[nodiscard]] std::shared_ptr<Runner> winner() {
130 run();
131 return _winner;
132 }
133
134 [[nodiscard]] size_t winner_index() const {
135 return _winner_index;
136 }
137
138 [[nodiscard]] size_t winner_index() {
139 run();
140 return _winner_index;
141 }
142
143 [[nodiscard]] bool finished() const noexcept {
144 return _winner != nullptr && _winner->finished();
145 }
146
147 // Adds a Runner to the race, throws if the race is already over.
148 void add_runner(std::shared_ptr<Runner>);
149
150 using const_iterator =
151 typename std::vector<std::shared_ptr<Runner>>::const_iterator;
152
153 [[nodiscard]] const_iterator begin() const noexcept {
154 return _runners.cbegin();
155 }
156
157 [[nodiscard]] const_iterator end() const noexcept {
158 return _runners.cend();
159 }
160
161 // Returns an iterator pointing to the first Runner in the Race.
162 [[nodiscard]] const_iterator cbegin() const noexcept {
163 return _runners.cbegin();
164 }
165
166 // Returns an iterator pointing to one past the last Runner in the Race.
167 [[nodiscard]] const_iterator cend() const noexcept {
168 return _runners.cend();
169 }
170
171 // Returns \c true if there are no Runners in the race, and \c false
172 // otherwise.
173 // std::vector::empty is noexcept
174 [[nodiscard]] bool empty() const noexcept {
175 return _runners.empty();
176 }
177
178 // std::vector::size is noexcept
179 [[nodiscard]] size_t number_of_runners() const noexcept {
180 return _runners.size();
181 }
182
183 // Runs the race to completion.
184 void run();
185
186 // Runs the race for the specified amount of time.
187 void run_for(std::chrono::nanoseconds);
188
189 // Runs until \p func returns \c true, or the race is over. This
190 // repeatedly calls Race::run_for for \p check_interval, and then checks
191 // whether or not \p func() returns true. The object \p func can be any
192 // callable object with 0 parameters and that returns a bool.
193 // This is definitely tested but doesn't show up in the code coverage for
194 // some reason.
195 template <typename Func>
196 void run_until(Func&& func) {
197 static_assert(
198 std::is_same_v<std::invoke_result_t<Func>, bool>,
199 "the result type of calling an object of type Func (the template "
200 "parameter) must be bool!");
201 if (empty()) {
202 LIBSEMIGROUPS_EXCEPTION("no runners given, cannot run_until");
203 }
204 // while (!func() && _winner == nullptr) {
205 // // if _winner != nullptr, then the race is over.
206 // run_for(check_interval);
207 // check_interval *= 2;
208 // }
209 // return;
210 report_default("{}: running until predicate returns true or finished\n",
211 report_prefix());
212 run_func([&func](std::shared_ptr<Runner> r) -> void {
213 r->run_until(std::forward<Func>(func));
214 });
215 }
216
217 template <typename T>
218 bool has() const {
219 return find_runner<T>() != nullptr;
220 }
221
222 template <typename T>
223 [[nodiscard]] std::shared_ptr<T> find_runner() const {
224 static_assert(std::is_base_of<Runner, T>::value,
225 "the template parameter must be derived from Runner");
226 // We use find_if so that this works even if we haven't computed
227 // anything at all.
228 auto it = std::find_if(_runners.begin(),
229 _runners.end(),
230 [](std::shared_ptr<Runner> const& m) {
231 auto& r = *(m.get());
232 return typeid(r) == typeid(T);
233 });
234 if (it != _runners.end()) {
235 return std::static_pointer_cast<T>(*it);
236 } else {
237 return nullptr;
238 }
239 }
240
241 void erase_runners(const_iterator pos) {
242 _runners.erase(pos);
243 }
244
245 void erase_runners(const_iterator first, const_iterator last) {
246 _runners.erase(first, last);
247 }
248
249 private:
250 void clear_runners_after_race() {
251 if (_winner != nullptr) {
252 for (auto rnnr : _runners) {
253 if (rnnr != _winner) {
254 rnnr.reset();
255 }
256 }
257 _runners.clear();
258 _runners.push_back(_winner);
259 }
260 }
261
262 // Runs the callable object \p func on every Runner in parallel.
263 template <typename Func>
264 void run_func(Func&& func) {
265 static_assert(
266 std::is_same_v<std::invoke_result_t<Func, std::shared_ptr<Runner>>,
267 void>,
268 "the result type of calling an object of type Func (the template "
269 "parameter) must be void!");
270 using libsemigroups::detail::string_time;
271 LIBSEMIGROUPS_ASSERT(!empty());
273 if (_winner == nullptr) {
274 size_t nr_threads = std::min(_runners.size(), _max_threads);
275 if (nr_threads == 1) {
276 report_default("{}: using 0 additional threads\n", report_prefix());
277 func(_runners.at(0));
278 if (_runners.at(0)->success()) {
279 _winner = _runners.at(0);
280 _winner_index = 0;
281 clear_runners_after_race();
282 }
283 report_default("{}: elapsed time {}\n",
285 string_time(delta(start_time())));
286 return;
287 }
288 for (size_t i = 0; i < _runners.size(); ++i) {
289 if (_runners[i]->success()) {
290 report_default("{}: using 0 additional threads\n",
291 report_prefix());
292 _winner = _runners[i];
293 _winner_index = i;
294 report_default("{}: #{} already finished successfully!\n",
296 i);
297 // delete the other runners?
298 clear_runners_after_race();
299 return;
300 }
301 }
302
303 std::vector<std::thread::id> tids(_runners.size(),
305
306 report_default("{}: using {} / {} additional threads\n",
308 nr_threads,
310 detail::Timer tmr;
311 LIBSEMIGROUPS_ASSERT(nr_threads != 0);
312
313 auto thread_func = [this, &func, &tids](size_t pos) {
314 tids[pos] = std::this_thread::get_id();
315 try {
316 func(_runners.at(pos));
317 } catch (std::exception const& e) {
318 size_t tid = thread_id(tids[pos]);
319 report_default("{}: exception thrown by #{}:\n{}\n",
321 tid,
322 e.what());
323 return;
324 }
325 // Stop two Runner* objects from killing each other
326 {
327 std::lock_guard<std::mutex> lg(_mtx);
328 if (_runners.at(pos)->success()) {
329 for (auto it = _runners.begin(); it < _runners.begin() + pos;
330 it++) {
331 (*it)->kill();
332 }
333 for (auto it = _runners.begin() + pos + 1; it < _runners.end();
334 it++) {
335 (*it)->kill();
336 }
337 }
338 }
339 };
340 reset_thread_ids();
341
342 std::vector<std::thread> t;
343 for (size_t i = 0; i < nr_threads; ++i) {
344 t.push_back(std::thread(thread_func, i));
345 }
346 for (size_t i = 0; i < nr_threads; ++i) {
347 t.at(i).join();
348 }
349 report_default("{}: elapsed time {}\n",
351 string_time(delta(start_time())));
352 for (auto method = _runners.begin(); method < _runners.end();
353 ++method) {
354 if ((*method)->success()) {
355 LIBSEMIGROUPS_ASSERT(_winner == nullptr);
356 _winner = *method;
357 _winner_index = method - _runners.begin();
358 size_t tid = thread_id(tids.at(method - _runners.begin()));
359 report_default("{}: #{} is the winner!\n", report_prefix(), tid);
360 break;
361 }
362 }
363 clear_runners_after_race();
364 }
365 }
366 };
367 } // namespace detail
368} // namespace libsemigroups
369
370#endif // LIBSEMIGROUPS_DETAIL_RACE_HPP_
T at(T... args)
Reporter const & reset_start_time() const
Reset the start time (and last report) to now.
Definition runner.hpp:255
std::string const & report_prefix() const noexcept
Get the current prefix string for reporting.
Definition runner.hpp:325
static std::chrono::nanoseconds delta(std::chrono::high_resolution_clock::time_point const &t)
The time between a given point and now.
Definition runner.hpp:70
time_point start_time() const noexcept
Get the start time.
Definition runner.hpp:243
T find_if(T... args)
T forward(T... args)
T get_id(T... args)
Undefined const UNDEFINED
Value for something undefined.
#define LIBSEMIGROUPS_EXCEPTION(...)
Throw a LibsemigroupsException.
Definition exception.hpp:99
T hardware_concurrency(T... args)
T min(T... args)
T move(T... args)
Namespace for everything in the libsemigroups library.
Definition action.hpp:44
void report_default(std::string_view sv, Args &&... args)
No doc.
Definition report.hpp:162
T static_pointer_cast(T... args)
T push_back(T... args)
T what(T... args)