OR-Tools  9.3
subsolver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <vector>
21
22#include "absl/flags/flag.h"
23#include "absl/strings/string_view.h"
24#include "absl/synchronization/mutex.h"
25#include "absl/time/clock.h"
26#include "absl/time/time.h"
28#if !defined(__PORTABLE_PLATFORM__)
30#endif // __PORTABLE_PLATFORM__
31
32namespace operations_research {
33namespace sat {
34
35namespace {
36
37// Returns the next SubSolver index from which to call GenerateTask(). Note that
38// only SubSolvers for which TaskIsAvailable() is true are considered. Return -1
39// if no SubSolver can generate a new task.
40//
41// For now we use a really basic logic: call the least frequently called.
42int NextSubsolverToSchedule(
43 const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
44 const std::vector<int64_t>& num_generated_tasks) {
45 int best = -1;
46 for (int i = 0; i < subsolvers.size(); ++i) {
47 if (subsolvers[i]->TaskIsAvailable()) {
48 if (best == -1 || num_generated_tasks[i] < num_generated_tasks[best]) {
49 best = i;
50 }
51 }
52 }
53 if (best != -1) VLOG(1) << "Scheduling " << subsolvers[best]->name();
54 return best;
55}
56
57void SynchronizeAll(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
58 for (const auto& subsolver : subsolvers) subsolver->Synchronize();
59}
60
61} // namespace
62
63void SequentialLoop(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
64 int64_t task_id = 0;
65 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
66 while (true) {
67 SynchronizeAll(subsolvers);
68 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
69 if (best == -1) break;
70 num_generated_tasks[best]++;
71 subsolvers[best]->GenerateTask(task_id++)();
72 }
73}
74
75#if defined(__PORTABLE_PLATFORM__)
76
77// On portable platform, we don't support multi-threading for now.
78
80 const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
81 int num_threads) {
82 SequentialLoop(subsolvers);
83}
84
86 const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads,
87 int batch_size) {
88 SequentialLoop(subsolvers);
89}
90
91#else // __PORTABLE_PLATFORM__
92
94 const std::vector<std::unique_ptr<SubSolver>>& subsolvers, int num_threads,
95 int batch_size) {
96 CHECK_GT(num_threads, 0);
97 CHECK_GT(batch_size, 0);
98 if (batch_size == 1) {
99 return SequentialLoop(subsolvers);
100 }
101
102 int64_t task_id = 0;
103 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
104 while (true) {
105 SynchronizeAll(subsolvers);
106
107 // TODO(user): We could reuse the same ThreadPool as long as we wait for all
108 // the task in a batch to finish before scheduling new ones. Not sure how
109 // to easily do that, so for now we just recreate the pool for each batch.
110 ThreadPool pool("DeterministicLoop", num_threads);
111 pool.StartWorkers();
112
113 int num_in_batch = 0;
114 for (int t = 0; t < batch_size; ++t) {
115 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
116 if (best == -1) break;
117 ++num_in_batch;
118 num_generated_tasks[best]++;
119 pool.Schedule(subsolvers[best]->GenerateTask(task_id++));
120 }
121 if (num_in_batch == 0) break;
122 }
123}
124
126 const std::vector<std::unique_ptr<SubSolver>>& subsolvers,
127 int num_threads) {
128 CHECK_GT(num_threads, 0);
129 if (num_threads == 1) {
130 return SequentialLoop(subsolvers);
131 }
132
133 // The mutex will protect these two fields. This is used to only keep
134 // num_threads task in-flight and detect when the search is done.
135 absl::Mutex mutex;
136 absl::CondVar thread_available_condition;
137 int num_scheduled_and_not_done = 0;
138
139 ThreadPool pool("NonDeterministicLoop", num_threads);
140 pool.StartWorkers();
141
142 // The lambda below are using little space, but there is no reason
143 // to create millions of them, so we use the blocking nature of
144 // pool.Schedule() when the queue capacity is set.
145 int64_t task_id = 0;
146 std::vector<int64_t> num_generated_tasks(subsolvers.size(), 0);
147 while (true) {
148 bool all_done = false;
149 {
150 absl::MutexLock mutex_lock(&mutex);
151
152 // The stopping condition is that we do not have anything else to generate
153 // once all the task are done and synchronized.
154 if (num_scheduled_and_not_done == 0) all_done = true;
155
156 // Wait if num_scheduled_and_not_done == num_threads.
157 if (num_scheduled_and_not_done == num_threads) {
158 thread_available_condition.Wait(&mutex);
159 }
160 }
161
162 SynchronizeAll(subsolvers);
163 const int best = NextSubsolverToSchedule(subsolvers, num_generated_tasks);
164 if (best == -1) {
165 if (all_done) break;
166
167 // It is hard to know when new info will allows for more task to be
168 // scheduled, so for now we just sleep for a bit. Note that in practice We
169 // will never reach here except at the end of the search because we can
170 // always schedule LNS threads.
171 absl::SleepFor(absl::Milliseconds(1));
172 continue;
173 }
174
175 // Schedule next task.
176 num_generated_tasks[best]++;
177 {
178 absl::MutexLock mutex_lock(&mutex);
179 num_scheduled_and_not_done++;
180 }
181 std::function<void()> task = subsolvers[best]->GenerateTask(task_id++);
182 const std::string name = subsolvers[best]->name();
183 pool.Schedule([task, num_threads, name, &mutex, &num_scheduled_and_not_done,
184 &thread_available_condition]() {
185 task();
186
187 absl::MutexLock mutex_lock(&mutex);
188 VLOG(1) << name << " done.";
189 num_scheduled_and_not_done--;
190 if (num_scheduled_and_not_done == num_threads - 1) {
191 thread_available_condition.SignalAll();
192 }
193 });
194 }
195}
196
197#endif // __PORTABLE_PLATFORM__
198
199} // namespace sat
200} // namespace operations_research
#define CHECK_GT(val1, val2)
Definition: base/logging.h:708
#define VLOG(verboselevel)
Definition: base/logging.h:984
void Schedule(std::function< void()> closure)
Definition: threadpool.cc:77
const std::string name
void DeterministicLoop(const std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads, int batch_size)
Definition: subsolver.cc:93
void SequentialLoop(const std::vector< std::unique_ptr< SubSolver > > &subsolvers)
Definition: subsolver.cc:63
void NonDeterministicLoop(const std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads)
Definition: subsolver.cc:125
Collection of objects used to extend the Constraint Solver library.