OR-Tools  9.2
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
19
20#if !defined(__PORTABLE_PLATFORM__)
21#include "absl/synchronization/mutex.h"
22#include "absl/time/clock.h"
23#endif // __PORTABLE_PLATFORM__
24
25namespace operations_research {
26namespace sat {
27
28namespace {
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.
35int 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
50void SynchronizeAll(const std::vector<std::unique_ptr<SubSolver>>& subsolvers) {
51 for (const auto& subsolver : subsolvers) subsolver->Synchronize();
52}
53
54} // namespace
55
56void 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:707
#define VLOG(verboselevel)
Definition: base/logging.h:983
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 SequentialLoop(const std::vector< std::unique_ptr< SubSolver > > &subsolvers)
Definition: subsolver.cc:56
void NonDeterministicLoop(const std::vector< std::unique_ptr< SubSolver > > &subsolvers, int num_threads)
Definition: subsolver.cc:118
Collection of objects used to extend the Constraint Solver library.