forked from google/or-tools
-
Notifications
You must be signed in to change notification settings - Fork 2
/
time_limit.h
541 lines (479 loc) · 20 KB
/
time_limit.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_UTIL_TIME_LIMIT_H_
#define OR_TOOLS_UTIL_TIME_LIMIT_H_
#include <algorithm>
#include <atomic>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/flags/declare.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/clock.h"
#include "ortools/base/timer.h"
#include "ortools/base/types.h"
#include "ortools/util/running_stat.h"
/**
* Enables changing the behavior of the TimeLimit class to use -b usertime
* instead of \b walltime. This is mainly useful for benchmarks.
*/
ABSL_DECLARE_FLAG(bool, time_limit_use_usertime);
namespace operations_research {
/**
* A simple class to enforce both an elapsed time limit and a deterministic time
* limit in the same thread as a program.
* The idea is to call LimitReached() as often as possible, until it returns
* false. The program should then abort as fast as possible.
*
* The deterministic limit is used to ensure reproductibility. As a consequence
* the deterministic time has to be advanced manually using the method
* AdvanceDeterministicTime().
*
* The call itself is as fast as CycleClock::Now() + a few trivial instructions.
*
* The limit is very conservative: it returns true (i.e. the limit is reached)
* when current_time + max(T, ε) >= limit_time, where ε is a small constant (see
* TimeLimit::kSafetyBufferSeconds), and T is the maximum measured time interval
* between two consecutive calls to LimitReached() over the last kHistorySize
* calls (so that we only consider "recent" history).
* This is made so that the probability of actually exceeding the time limit is
* small, without aborting too early.
*
* The deterministic time limit can be logged at a more granular level: the
* method TimeLimit::AdvanceDeterministicTime takes an optional string argument:
* the name of a counter. In debug mode, the time limit object computes also the
* elapsed time for each named counter separately, and these values can be used
* to determine the coefficients for computing the deterministic duration from
* the number of operations. The values of the counters can be printed using
* TimeLimit::DebugString(). There is no API to access the values of the
* counters directly, because they do not exist in optimized mode.
*
* The basic steps for determining coefficients for the deterministic time are:
* 1. Run the code in debug mode to collect the values of the deterministic time
* counters. Unless the algorithm is different in optimized mode, the values
* of the deterministic counters in debug mode will be the same as in
* optimized mode.
* 2. Run the code in optimized mode to measure the real (CPU) time of the whole
* benchmark.
* 3. Determine the coefficients for deterministic time from the real time and
* the values of the deterministic counters, e. g. by solving the equations
* C_1*c_1 + C_2*c_2 + ... + C_N*c_N + Err = T
* where C_1 is the unknown coefficient for counter c_1, Err is the random
* measurement error and T is the measured real time. The equation can be
* solved e.g. using the least squares method.
*
* Note that in optimized mode, the counters are disabled for performance
* reasons, and calling AdvanceDeterministicTime(duration, counter_name) is
* equivalent to calling AdvanceDeterministicTime(duration).
*/
// TODO(user): The expression "deterministic time" should be replaced with
// "number of operations" to avoid confusion with "real" time.
class TimeLimit {
public:
static const double kSafetyBufferSeconds; // See the .cc for the value.
static const int kHistorySize;
/**
* Sets the elapsed and the deterministic time.
* The elapsed time is based on the wall time and the counter starts 'now'.
* The deterministic time has to be manually advanced using the method
* AdvanceDeterministicTime().
*
* Use an infinite limit value to ignore a limit.
*/
explicit TimeLimit(
double limit_in_seconds,
double deterministic_limit = std::numeric_limits<double>::infinity());
TimeLimit() : TimeLimit(std::numeric_limits<double>::infinity()) {}
TimeLimit(const TimeLimit&) = delete;
TimeLimit& operator=(const TimeLimit&) = delete;
/**
* Creates a time limit object that uses infinite time for wall time and
* deterministic time.
*/
static std::unique_ptr<TimeLimit> Infinite() {
return std::make_unique<TimeLimit>(std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity());
}
/**
* Creates a time limit object that puts limit only on the deterministic time.
*/
static std::unique_ptr<TimeLimit> FromDeterministicTime(
double deterministic_limit) {
return std::make_unique<TimeLimit>(std::numeric_limits<double>::infinity(),
deterministic_limit);
}
/**
* Creates a time limit object initialized from an object that provides
* methods \c max_time_in_seconds() and max_deterministic_time(). This method
* is designed specifically to work with solver parameter protos, e.g.
* \c BopParameters, \c MipParameters and \c SatParameters.
*/
template <typename Parameters>
static std::unique_ptr<TimeLimit> FromParameters(
const Parameters& parameters) {
return std::make_unique<TimeLimit>(parameters.max_time_in_seconds(),
parameters.max_deterministic_time());
}
/**
* Returns true when the external limit is true, or the deterministic time is
* over the deterministic limit or if the next time \c LimitReached() is
* called is likely to be over the time limit. See toplevel comment. Once it
* has returned true, it is guaranteed to always return true.
*/
bool LimitReached();
/**
* Returns the time left on this limit, or 0 if the limit was reached (it
* never returns a negative value). Note that it might return a positive
* value even though \c LimitReached() would return true; because the latter
* is conservative (see toplevel comment). If \c LimitReached() was actually
* called and did return \b true, though, this will always return 0.
*
* If the TimeLimit was constructed with \b infinity as the limit, this will
* always return infinity.
*
* Note that this function is not optimized for speed as \c LimitReached() is.
*/
double GetTimeLeft() const;
/**
* Returns the remaining deterministic time before \c LimitReached() returns
* true due to the deterministic limit.
* If the \c TimeLimit was constructed with \b infinity as the deterministic
* limit (default value), this will always return infinity.
*/
double GetDeterministicTimeLeft() const {
return std::max(0.0, deterministic_limit_ - elapsed_deterministic_time_);
}
/**
* Advances the deterministic time. For reproducibility reasons, the
* deterministic time doesn't advance automatically as the regular elapsed
* time does.
*/
inline void AdvanceDeterministicTime(double deterministic_duration) {
DCHECK_LE(0.0, deterministic_duration);
elapsed_deterministic_time_ += deterministic_duration;
}
/**
* Advances the deterministic time. For reproducibility reasons, the
* deterministic time doesn't advance automatically as the regular elapsed
* time does.
*
* In debug mode, this method also updates the deterministic time counter with
* the given name. In optimized mode, this method is equivalent to
* \c AdvanceDeterministicTime(double).
*/
inline void AdvanceDeterministicTime(double deterministic_duration,
const char* counter_name) {
AdvanceDeterministicTime(deterministic_duration);
#ifndef NDEBUG
deterministic_counters_[counter_name] += deterministic_duration;
#endif
}
/**
* Returns the time elapsed in seconds since the construction of this object.
*/
double GetElapsedTime() const {
return 1e-9 * (absl::GetCurrentTimeNanos() - start_ns_);
}
/**
* Returns the elapsed deterministic time since the construction of this
* object. That corresponds to the sum of all deterministic durations passed
* as an argument to \c AdvanceDeterministicTime() calls.
*/
double GetElapsedDeterministicTime() const {
return elapsed_deterministic_time_;
}
/**
* Registers the external Boolean to check when LimitReached() is called.
* This is used to mark the limit as reached through an external Boolean,
* i.e. \c LimitReached() returns true when the value of
* external_boolean_as_limit is true whatever the time limits are.
*
* Note that users of the TimeLimit can modify the provided atomic for their
* own internal logic (see SharedTimeLimit::Stop() for example).
*/
void RegisterExternalBooleanAsLimit(
std::atomic<bool>* external_boolean_as_limit) {
external_boolean_as_limit_ = external_boolean_as_limit;
}
/**
* Same as RegisterExternalBooleanAsLimit() but register a second Boolean.
* We have seen that in some situation two Booleans are required, but we
* haven't needed more than two yet.
*
* Note(user): It was also easier to just add a second one rather to refactor
* all clients to use a vector for instance.
*/
void RegisterSecondaryExternalBooleanAsLimit(
std::atomic<bool>* external_boolean_as_limit) {
secondary_external_boolean_as_limit_ = external_boolean_as_limit;
}
/**
* Returns the current external Boolean limit.
*/
std::atomic<bool>* ExternalBooleanAsLimit() const {
return external_boolean_as_limit_;
}
/**
* Sets new time limits. Note that this does not reset the running max nor
* any registered external Boolean.
*/
template <typename Parameters>
void ResetLimitFromParameters(const Parameters& parameters);
/**
* Sets time limits equal to the min of the current one and the passed limit.
* If the passed limit contain an external Boolean, replace the current one
* with it. Not that this does not change the secondary Boolean.
*/
void MergeWithGlobalTimeLimit(TimeLimit* other);
/**
* Overwrites the deterministic time limit with the new value.
*/
void ChangeDeterministicLimit(double new_limit) {
deterministic_limit_ = new_limit;
}
/**
* Queries the deterministic time limit.
*/
double GetDeterministicLimit() const { return deterministic_limit_; }
/**
* Returns information about the time limit object in a human-readable form.
*/
std::string DebugString() const;
private:
void ResetTimers(double limit_in_seconds, double deterministic_limit);
mutable int64_t start_ns_; // Not const! this is initialized after
// instruction counter initialization.
int64_t last_ns_;
int64_t limit_ns_; // Not const! See the code of LimitReached().
const int64_t safety_buffer_ns_;
RunningMax<int64_t> running_max_;
// Only used when FLAGS_time_limit_use_usertime is true.
UserTimer user_timer_;
double limit_in_seconds_;
double deterministic_limit_;
double elapsed_deterministic_time_;
std::atomic<bool>* external_boolean_as_limit_ = nullptr;
std::atomic<bool>* secondary_external_boolean_as_limit_ = nullptr;
#ifndef NDEBUG
// Contains the values of the deterministic time counters.
absl::flat_hash_map<std::string, double> deterministic_counters_;
#endif
friend class NestedTimeLimit;
friend class ParallelTimeLimit;
};
// Wrapper around TimeLimit to make it thread safe and add Stop() support.
class SharedTimeLimit {
public:
explicit SharedTimeLimit(TimeLimit* time_limit)
: time_limit_(time_limit), stopped_boolean_(false) {
// We use the one already registered if present or ours otherwise.
stopped_ = time_limit->ExternalBooleanAsLimit();
if (stopped_ == nullptr) {
stopped_ = &stopped_boolean_;
time_limit->RegisterExternalBooleanAsLimit(stopped_);
}
}
~SharedTimeLimit() {
if (stopped_ == &stopped_boolean_) {
time_limit_->RegisterExternalBooleanAsLimit(nullptr);
}
}
bool LimitReached() const {
// Note, time_limit_->LimitReached() is not const, and changes internal
// state of time_limit_, hence we need a writer's lock.
absl::MutexLock lock(&mutex_);
return time_limit_->LimitReached();
}
void Stop() {
absl::MutexLock lock(&mutex_);
*stopped_ = true;
}
void UpdateLocalLimit(TimeLimit* local_limit) {
absl::MutexLock lock(&mutex_);
local_limit->MergeWithGlobalTimeLimit(time_limit_);
}
void AdvanceDeterministicTime(double deterministic_duration) {
absl::MutexLock lock(&mutex_);
time_limit_->AdvanceDeterministicTime(deterministic_duration);
}
double GetTimeLeft() const {
absl::ReaderMutexLock lock(&mutex_);
return time_limit_->GetTimeLeft();
}
double GetElapsedDeterministicTime() const {
absl::ReaderMutexLock lock(&mutex_);
return time_limit_->GetElapsedDeterministicTime();
}
std::atomic<bool>* ExternalBooleanAsLimit() const {
absl::ReaderMutexLock lock(&mutex_);
// We can simply return the "external bool" and remain thread-safe because
// it's wrapped in std::atomic.
return time_limit_->ExternalBooleanAsLimit();
}
private:
mutable absl::Mutex mutex_;
TimeLimit* time_limit_ ABSL_GUARDED_BY(mutex_);
std::atomic<bool> stopped_boolean_ ABSL_GUARDED_BY(mutex_);
std::atomic<bool>* stopped_ ABSL_GUARDED_BY(mutex_);
};
/**
* Provides a way to nest time limits for algorithms where a certain part of
* the computation is bounded not just by the overall time limit, but also by a
* stricter time limit specific just for this particular part.
*
* This class takes a base time limit object (the overall time limit) and the
* part-specific time limit, and creates a new time limit object for the part.
* This new time limit object will expire when either the overall time limit
* expires or when the part-specific time limit expires.
*
* Example usage:
* \code
TimeLimit overall_time_limit(...);
NestedTimeLimit subalgorith_time_limit(&overall_time_limit,
subalgorithm_limit_in_seconds,
subalgorithm_deterministic_limit);
RunTheSubalgorithm(subalgorithm_time_limit.GetTimeLimit());
\endcode
*
* Note that remaining wall time in the base time limit is decreasing
* "automatically", but the deterministic time needs to be updated manually.
* This update is done only once, during the destruction of the nested time
* limit object. To track the deterministic time properly, the user must avoid
* modifying the base time limit object when a nested time limit exists.
*
* The nested time limits supports the external time limit condition in the
* sense, that if the overall time limit has an external boolean registered, the
* nested time limit object will use the same boolean value as an external time
* limit too.
*/
class NestedTimeLimit {
public:
/**
* Creates the nested time limit. Note that 'base_time_limit' must remain
* valid for the whole lifetime of the nested time limit object.
*/
NestedTimeLimit(TimeLimit* base_time_limit, double limit_in_seconds,
double deterministic_limit);
// This type is neither copyable nor movable.
NestedTimeLimit(const NestedTimeLimit&) = delete;
NestedTimeLimit& operator=(const NestedTimeLimit&) = delete;
/**
* Updates elapsed deterministic time in the base time limit object.
*/
~NestedTimeLimit();
/**
* Creates a time limit object initialized from a base time limit and an
* object that provides methods max_time_in_seconds() and
* max_deterministic_time(). This method is designed specifically to work with
* solver parameter protos, e.g. BopParameters, MipParameters and
* SatParameters.
*/
template <typename Parameters>
static std::unique_ptr<NestedTimeLimit> FromBaseTimeLimitAndParameters(
TimeLimit* time_limit, const Parameters& parameters) {
return std::make_unique<NestedTimeLimit>(
time_limit, parameters.max_time_in_seconds(),
parameters.max_deterministic_time());
}
/**
* Returns a time limit object that represents the combination of the overall
* time limit and the part-specific time limit. The returned time limit object
* is owned by the nested time limit object that returns it, and it will
* remain valid until the nested time limit object is destroyed.
*/
TimeLimit* GetTimeLimit() { return &time_limit_; }
private:
TimeLimit* const base_time_limit_;
TimeLimit time_limit_;
};
// ################## Implementations below #####################
inline TimeLimit::TimeLimit(double limit_in_seconds, double deterministic_limit)
: safety_buffer_ns_(static_cast<int64_t>(kSafetyBufferSeconds * 1e9)),
running_max_(kHistorySize),
external_boolean_as_limit_(nullptr) {
ResetTimers(limit_in_seconds, deterministic_limit);
}
inline void TimeLimit::ResetTimers(double limit_in_seconds,
double deterministic_limit) {
elapsed_deterministic_time_ = 0.0;
deterministic_limit_ = deterministic_limit;
if (absl::GetFlag(FLAGS_time_limit_use_usertime)) {
user_timer_.Start();
limit_in_seconds_ = limit_in_seconds;
}
start_ns_ = absl::GetCurrentTimeNanos();
last_ns_ = start_ns_;
// Note that duration arithmetic is properly saturated.
limit_ns_ = (absl::Seconds(limit_in_seconds) + absl::Nanoseconds(start_ns_)) /
absl::Nanoseconds(1);
}
template <typename Parameters>
inline void TimeLimit::ResetLimitFromParameters(const Parameters& parameters) {
ResetTimers(parameters.max_time_in_seconds(),
parameters.max_deterministic_time());
}
inline void TimeLimit::MergeWithGlobalTimeLimit(TimeLimit* other) {
if (other == nullptr) return;
ResetTimers(
std::min(GetTimeLeft(), other->GetTimeLeft()),
std::min(GetDeterministicTimeLeft(), other->GetDeterministicTimeLeft()));
if (other->ExternalBooleanAsLimit() != nullptr) {
RegisterExternalBooleanAsLimit(other->ExternalBooleanAsLimit());
}
}
inline bool TimeLimit::LimitReached() {
if (external_boolean_as_limit_ != nullptr &&
external_boolean_as_limit_->load()) {
return true;
}
if (secondary_external_boolean_as_limit_ != nullptr &&
secondary_external_boolean_as_limit_->load()) {
return true;
}
if (GetDeterministicTimeLeft() <= 0.0) {
return true;
}
const int64_t current_ns = absl::GetCurrentTimeNanos();
running_max_.Add(std::max(safety_buffer_ns_, current_ns - last_ns_));
last_ns_ = current_ns;
if (current_ns + running_max_.GetCurrentMax() >= limit_ns_) {
if (absl::GetFlag(FLAGS_time_limit_use_usertime)) {
// To avoid making many system calls, we only check the user time when
// the "absolute" time limit has been reached. Note that the user time
// should advance more slowly, so this is correct.
const double time_left_s = limit_in_seconds_ - user_timer_.Get();
if (time_left_s > kSafetyBufferSeconds) {
limit_ns_ = static_cast<int64_t>(time_left_s * 1e9) + last_ns_;
return false;
}
}
// To ensure that future calls to LimitReached() will return true.
limit_ns_ = 0;
return true;
}
return false;
}
inline double TimeLimit::GetTimeLeft() const {
if (limit_ns_ == kint64max) return std::numeric_limits<double>::infinity();
const int64_t delta_ns = limit_ns_ - absl::GetCurrentTimeNanos();
if (delta_ns < 0) return 0.0;
if (absl::GetFlag(FLAGS_time_limit_use_usertime)) {
return std::max(limit_in_seconds_ - user_timer_.Get(), 0.0);
} else {
return delta_ns * 1e-9;
}
}
} // namespace operations_research
#endif // OR_TOOLS_UTIL_TIME_LIMIT_H_