Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
subsolver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <cstdint>
17#include <functional>
18#include <memory>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/flags/flag.h"
24#include "absl/log/check.h"
25#include "absl/strings/string_view.h"
26#include "absl/synchronization/blocking_counter.h"
27#include "absl/synchronization/mutex.h"
28#include "absl/time/clock.h"
29#include "absl/time/time.h"
30#include "absl/types/span.h"
32#include "ortools/base/timer.h"
33#if !defined(__PORTABLE_PLATFORM__)
35#endif // __PORTABLE_PLATFORM__
36
37namespace operations_research {
38namespace sat {
39
40namespace {
41
42// Returns the next SubSolver index from which to call GenerateTask(). Note that
43// only SubSolvers for which TaskIsAvailable() is true are considered. Return -1
44// if no SubSolver can generate a new task.
45//
46// For now we use a really basic logic: call the least frequently called.
47int NextSubsolverToSchedule(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
48 absl::Span<const int64_t> num_generated_tasks) {
49 int best = -1;
50 for (int i = 0; i < subsolvers.size(); ++i) {
51 if (subsolvers[i] == nullptr) continue;
52 if (subsolvers[i]->TaskIsAvailable()) {
53 if (best == -1 || num_generated_tasks[i] < num_generated_tasks[best]) {
54 best = i;
55 }
56 }
57 }
58 if (best != -1) VLOG(1) << "Scheduling " << subsolvers[best]->name();
59 return best;
60}
61
62void ClearSubsolversThatAreDone(
63 const std::vector<int>& num_in_flight_per_subsolvers,
64 std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
65 for (int i = 0; i < subsolvers.size(); ++i) {
66 if (subsolvers[i] == nullptr) continue;
67 if (num_in_flight_per_subsolvers[i] > 0) continue;
68 if (subsolvers[i]->IsDone()) {
69 // We can free the memory used by this solver for good.
70 VLOG(1) << "Deleting " << subsolvers[i]->name();
71 subsolvers[i].reset();
72 continue;
73 }
74 }
75}
76
77void SynchronizeAll(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
78 for (const auto& subsolver : subsolvers) {
79 if (subsolver == nullptr) continue;
80 subsolver->Synchronize();
81 }
82}
83
84} // namespace
85
86void SequentialLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
87 int64_t task_id = 0;
88 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
89 std::vector<int> num_in_flight_per_subsolvers(subsolvers.size(), 0);
90 while (true) {
91 SynchronizeAll(subsolvers);
92 ClearSubsolversThatAreDone(num_in_flight_per_subsolvers, subsolvers);
93 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
94 if (best == -1) break;
95 num_generated_tasks[best]++;
96
97 WallTimer timer;
98 timer.Start();
99 subsolvers[best]->GenerateTask(task_id++)();
100 subsolvers[best]->AddTaskDuration(timer.Get());
101 }
102}
103
104#if defined(__PORTABLE_PLATFORM__)
105
106// On portable platform, we don't support multi-threading for now.
107
108void NonDeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
109 int num_threads) {
110 SequentialLoop(subsolvers);
111}
112
113void DeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
114 int num_threads, int batch_size, int max_num_batches) {
115 SequentialLoop(subsolvers);
116}
117
118#else // __PORTABLE_PLATFORM__
119
120void DeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
121 int num_threads, int batch_size, int max_num_batches) {
122 CHECK_GT(num_threads, 0);
123 CHECK_GT(batch_size, 0);
124 if (batch_size == 1) {
125 return SequentialLoop(subsolvers);
126 }
127
128 int64_t task_id = 0;
129 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
130 std::vector<int> num_in_flight_per_subsolvers(subsolvers.size(), 0);
131 std::vector<std::function<void()>> to_run;
132 std::vector<int> indices;
133 std::vector<double> timing;
134 to_run.reserve(batch_size);
135 ThreadPool pool("DeterministicLoop", num_threads);
136 pool.StartWorkers();
137 for (int batch_index = 0;; ++batch_index) {
138 VLOG(2) << "Starting deterministic batch of size " << batch_size;
139 SynchronizeAll(subsolvers);
140 ClearSubsolversThatAreDone(num_in_flight_per_subsolvers, subsolvers);
141
142 // We abort the loop after the last synchronize to properly reports final
143 // status in case max_num_batches is used.
144 if (max_num_batches > 0 && batch_index >= max_num_batches) break;
145
146 // We first generate all task to run in this batch.
147 // Note that we can't start the task right away since if a task finish
148 // before we schedule everything, we will not be deterministic.
149 to_run.clear();
150 indices.clear();
151 for (int t = 0; t < batch_size; ++t) {
152 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
153 if (best == -1) break;
154 num_in_flight_per_subsolvers[best]++;
155 num_generated_tasks[best]++;
156 to_run.push_back(subsolvers[best]->GenerateTask(task_id++));
157 indices.push_back(best);
158 }
159 if (to_run.empty()) break;
160
161 // Schedule each task.
162 timing.resize(to_run.size());
163 absl::BlockingCounter blocking_counter(static_cast<int>(to_run.size()));
164 for (int i = 0; i < to_run.size(); ++i) {
165 pool.Schedule(
166 [i, f = std::move(to_run[i]), &timing, &blocking_counter]() {
167 WallTimer timer;
168 timer.Start();
169 f();
170 timing[i] = timer.Get();
171 blocking_counter.DecrementCount();
172 });
173 }
174
175 // Wait for all tasks of this batch to be done before scheduling another
176 // batch.
177 blocking_counter.Wait();
178
179 // Update times.
180 num_in_flight_per_subsolvers.assign(subsolvers.size(), 0);
181 for (int i = 0; i < to_run.size(); ++i) {
182 subsolvers[indices[i]]->AddTaskDuration(timing[i]);
183 }
184 }
185}
186
187void NonDeterministicLoop(std::vector<std::unique_ptr<SubSolver>>& subsolvers,
188 const int num_threads) {
189 CHECK_GT(num_threads, 0);
190 if (num_threads == 1) {
191 return SequentialLoop(subsolvers);
192 }
193
194 // The mutex guards num_in_flight and num_in_flight_per_subsolvers.
195 // This is used to detect when the search is done.
196 absl::Mutex mutex;
197 int num_in_flight = 0; // Guarded by `mutex`.
198 std::vector<int> num_in_flight_per_subsolvers(subsolvers.size(), 0);
199
200 // Predicate to be used with absl::Condition to detect that num_in_flight <
201 // num_threads. Must only be called while locking `mutex`.
202 const auto num_in_flight_lt_num_threads = [&num_in_flight, num_threads]() {
203 return num_in_flight < num_threads;
204 };
205
206 ThreadPool pool("NonDeterministicLoop", num_threads);
207 pool.StartWorkers();
208
209 // The lambda below are using little space, but there is no reason
210 // to create millions of them, so we use the blocking nature of
211 // pool.Schedule() when the queue capacity is set.
212 int64_t task_id = 0;
213 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
214 while (true) {
215 // Set to true if no task is pending right now.
216 bool all_done = false;
217 {
218 // Wait if num_in_flight == num_threads.
219 const bool condition = mutex.LockWhenWithTimeout(
220 absl::Condition(&num_in_flight_lt_num_threads),
221 absl::Milliseconds(100));
222
223 // To support some "advanced" cancelation of subsolve, we still call
224 // synchronize every 0.1 seconds even if there is no worker available.
225 //
226 // TODO(user): We could also directly register callback to set stopping
227 // Boolean to false in a few places.
228 if (!condition) {
229 mutex.Unlock();
230 SynchronizeAll(subsolvers);
231 continue;
232 }
233
234 // The stopping condition is that we do not have anything else to generate
235 // once all the task are done and synchronized.
236 if (num_in_flight == 0) all_done = true;
237 mutex.Unlock();
238 }
239
240 SynchronizeAll(subsolvers);
241 {
242 // We need to do that while holding the lock since substask below might
243 // be currently updating the time via AddTaskDuration().
244 const absl::MutexLock mutex_lock(&mutex);
245 ClearSubsolversThatAreDone(num_in_flight_per_subsolvers, subsolvers);
246 }
247 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
248 if (best == -1) {
249 if (all_done) break;
250
251 // It is hard to know when new info will allows for more task to be
252 // scheduled, so for now we just sleep for a bit. Note that in practice We
253 // will never reach here except at the end of the search because we can
254 // always schedule LNS threads.
255 absl::SleepFor(absl::Milliseconds(1));
256 continue;
257 }
258
259 // Schedule next task.
260 num_generated_tasks[best]++;
261 {
262 absl::MutexLock mutex_lock(&mutex);
263 num_in_flight++;
264 num_in_flight_per_subsolvers[best]++;
265 }
266 std::function<void()> task = subsolvers[best]->GenerateTask(task_id++);
267 const std::string name = subsolvers[best]->name();
268 pool.Schedule([task = std::move(task), name, best, &subsolvers, &mutex,
269 &num_in_flight, &num_in_flight_per_subsolvers]() {
270 WallTimer timer;
271 timer.Start();
272 task();
273
274 const absl::MutexLock mutex_lock(&mutex);
275 DCHECK(subsolvers[best] != nullptr);
276 DCHECK_GT(num_in_flight_per_subsolvers[best], 0);
277 num_in_flight_per_subsolvers[best]--;
278 VLOG(1) << name << " done in " << timer.Get() << "s.";
279 subsolvers[best]->AddTaskDuration(timer.Get());
280 num_in_flight--;
281 });
282 }
283}
284
285#endif // __PORTABLE_PLATFORM__
286
287} // namespace sat
288} // namespace operations_research
double Get() const
Definition timer.h:46
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:32
void Schedule(std::function< void()> closure)
Definition threadpool.cc:83
const std::string name
A name for logging purposes.
void NonDeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, const int num_threads)
Definition subsolver.cc:187
void DeterministicLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads, int batch_size, int max_num_batches)
Definition subsolver.cc:120
void SequentialLoop(std::vector< std::unique_ptr< SubSolver > > &subsolvers)
Definition subsolver.cc:86
In SWIG mode, we don't want anything besides these top-level includes.