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