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