Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
stats.h
Go to the documentation of this file.
1// Copyright 2010-2024 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// Helper classes to track statistics of a program component.
15//
16// Usage example:
17// // Suppose you have a class that contains a factorization of a matrix B and
18// // a Solve() function to solve the linear system B.x = a.
19//
20// // You will hold your stats in a Stats stats_ class member:
21// struct Stats : public StatsGroup {
22// Stats() : StatsGroup("BasisFactorization"),
23// solve_time("solve_time", this),
24// input_vector_density("input_vector_density", this),
25// estimated_accuracy("estimated_accuracy", this) {}
26//
27// TimeDistribution solve_time;
28// RatioDistribution input_vector_density;
29//
30// // Values of a few components of B.x - a, updated on each solve.
31// DoubleDistribution estimated_accuracy;
32// }
33//
34// // You then add a few lines to your Solve() function:
35// void Solve() {
36// stats_.solve_time.StartTimer();
37// stats_.input_vector_density.Add(ComputeDensity());
38// ... // Do the work.
39// stats_.estimated_accuracy.Add(EstimateAccuracy());
40// stats_.solve_time.StopTimerAndAddElapsedTime();
41// }
42//
43// // Now, calling stats_.StatString() will give you a summary of your stats:
44// BasisFactorization {
45// solve_time : num [min, max] average std_deviation total
46// input_vector_density : num [min, max] average std_deviation
47// estimated_accuracy : num [min, max] average std_deviation
48// }
49//
50// For measuring time, another alternative is to use the SCOPED_TIME_STAT macro.
51// In our example above, you don't need to define the solve_time distribution
52// and you can just do:
53//
54// void Solve() {
55// SCOPED_TIME_STAT(&stats_);
56// ...
57// }
58//
59// This automatically adds a TimeDistribution with name "Solve" to stats_ and
60// times your function calls!
61//
62// IMPORTANT: The SCOPED_TIME_STAT() macro only does something if OR_STATS is
63// defined, so you need to build your code with blaze build --copt='-DOR_STATS'.
64// The idea is that by default the instrumentation is off. You can also use the
65// macro IF_STATS_ENABLED() that does nothing if OR_STATS is not defined or just
66// translates to its argument otherwise.
67
68#ifndef OR_TOOLS_UTIL_STATS_H_
69#define OR_TOOLS_UTIL_STATS_H_
70
71#include <cstdint>
72#include <map>
73#include <string>
74#include <vector>
75
76#include "absl/strings/string_view.h"
77#include "absl/time/time.h"
78#include "ortools/base/timer.h"
79
80namespace operations_research {
81
82// Returns the current thread's total memory usage in an human-readable string.
83std::string MemoryUsage();
84
85// Forward declaration.
86class StatsGroup;
87class TimeDistribution;
88
89// Base class for a statistic that can be pretty-printed.
90class Stat {
91 public:
92 explicit Stat(absl::string_view name) : name_(name) {}
93
94 // Also add this stat to the given group.
95 Stat(absl::string_view name, StatsGroup* group);
96 virtual ~Stat() {}
97
98 // Only used for display purposes.
99 std::string Name() const { return name_; }
100
101 // Returns a human-readable formatted line of the form "name:
102 // ValueAsString()".
103 std::string StatString() const;
104
105 // At display, stats are displayed by decreasing priority, then decreasing
106 // Sum(), then alphabetical order.
107 // Used to group the stats per category (timing, ratio, etc..,).
108 virtual int Priority() const { return 0; }
109
110 // By default return 0 for the sum. This makes it possible to sort stats by
111 // decreasing total time.
112 virtual double Sum() const { return 0; }
113
114 // Prints information about this statistic.
115 virtual std::string ValueAsString() const = 0;
116
117 // Is this stat worth printing? Usually false if nothing was measured.
118 virtual bool WorthPrinting() const = 0;
119
120 // Reset this statistic to the same state as if it was newly created.
121 virtual void Reset() = 0;
122
123 private:
124 const std::string name_;
125};
126
127// Base class to print a nice summary of a group of statistics.
129 public:
134
135 explicit StatsGroup(absl::string_view name)
136 : name_(name), stats_(), time_distributions_() {}
137
138 // This type is neither copyable nor movable.
139 StatsGroup(const StatsGroup&) = delete;
140 StatsGroup& operator=(const StatsGroup&) = delete;
141 ~StatsGroup();
142
143 // Registers a Stat, which will appear in the string returned by StatString().
144 // The Stat object must live as long as this StatsGroup.
145 void Register(Stat* stat);
146
147 // Returns this group name, followed by one line per Stat registered with this
148 // group (this includes the ones created by LookupOrCreateTimeDistribution()).
149 // Note that only the stats WorthPrinting() are printed.
150 std::string StatString() const;
151
152 // Changes the print ordering (will affect the order in which the stats
153 // registered with this group are printed via StatString()).
154 void SetPrintOrder(PrintOrder print_order) { print_order_ = print_order; }
155
156 // Returns and if needed creates and registers a TimeDistribution with the
157 // given name. Note that this involve a map lookup and his thus slower than
158 // directly accessing a TimeDistribution variable.
160
161 // Calls Reset() on all the statistics registered with this group.
162 void Reset();
163
164 private:
165 std::string name_;
167 std::vector<Stat*> stats_;
168 std::map<std::string, TimeDistribution*> time_distributions_;
169};
170
171// Base class to track and compute statistics about the distribution of a
172// sequence of double. We provide a few sub-classes below that differ in the way
173// the values are added to the sequence and in the way the stats are printed.
174class DistributionStat : public Stat {
175 public:
176 explicit DistributionStat(absl::string_view name);
178 DistributionStat(absl::string_view name, StatsGroup* group);
179 ~DistributionStat() override {}
180 void Reset() override;
181 bool WorthPrinting() const override { return num_ != 0; }
182
183 // Implemented by the subclasses.
184 std::string ValueAsString() const override = 0;
185
186 // Trivial statistics on all the values added so far.
187 double Sum() const override { return sum_; }
188 double Max() const { return max_; }
189 double Min() const { return min_; }
190 int64_t Num() const { return num_; }
191
192 // Get the average of the distribution or 0.0 if empty.
193 double Average() const;
194
195 // Get the standard deviation of the distribution or 0.0 if empty.
196 // We use the on-line algorithm of Welford described at
197 // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
198 // TODO(user): We could also use on top the Kahan summation algorithm to be
199 // even more precise but a bit slower too.
200 double StdDeviation() const;
201
202 protected:
203 // Adds a value to this sequence and updates the stats.
204 void AddToDistribution(double value);
205 double sum_;
206 double average_;
208 double min_;
209 double max_;
210 int64_t num_;
211};
212
213// Statistic on the distribution of a sequence of running times.
214// Also provides some facility to measure such time with the CPU cycle counter.
215//
216// TODO(user): Since we inherit from DistributionStat, we currently store the
217// sum of CPU cycles as a double internally. A better option is to use int64_t
218// because with the 53 bits of precision of a double, we will run into an issue
219// if the sum of times reaches 52 days for a 2GHz processor.
221 public:
222 explicit TimeDistribution(absl::string_view name)
223 : DistributionStat(name), timer_() {}
225 TimeDistribution(absl::string_view name, StatsGroup* group)
226 : DistributionStat(name, group), timer_() {}
227 std::string ValueAsString() const override;
228
229 // Time distributions have a high priority to be displayed first.
230 int Priority() const override { return 100; }
231
232 // Internally the TimeDistribution stores CPU cycles (to do a bit less work
233 // on each StopTimerAndAddElapsedTime()). Use this function to convert
234 // all the statistics of DistributionStat into seconds.
235 static double CyclesToSeconds(double num_cycles);
236
237 // Adds a time in seconds to this distribution.
238 void AddTimeInSec(double seconds);
239 void AddTime(absl::Duration duration) {
240 AddTimeInSec(absl::ToDoubleSeconds(duration));
241 }
242
243 // Adds a time in CPU cycles to this distribution.
244 void AddTimeInCycles(double cycles);
245
246 // Starts the timer in preparation of a StopTimerAndAddElapsedTime().
247 inline void StartTimer() { timer_.Restart(); }
248
249 // Adds the elapsed time since the last StartTimer() to the distribution and
250 // returns this time in CPU cycles.
252 const double cycles = static_cast<double>(timer_.GetCycles());
253 AddToDistribution(cycles);
254 return cycles;
255 }
256
257 private:
258 // Converts and prints a number of cycles in an human readable way using the
259 // proper time unit depending on the value (ns, us, ms, s, m or h).
260 static std::string PrintCyclesAsTime(double cycles);
261 CycleTimer timer_;
262};
263
264// Statistic on the distribution of a sequence of ratios, displayed as %.
266 public:
267 explicit RatioDistribution(absl::string_view name) : DistributionStat(name) {}
269 RatioDistribution(absl::string_view name, StatsGroup* group)
270 : DistributionStat(name, group) {}
271 std::string ValueAsString() const override;
272 void Add(double value);
273};
274
275// Statistic on the distribution of a sequence of doubles.
277 public:
278 explicit DoubleDistribution(absl::string_view name)
281 DoubleDistribution(absl::string_view name, StatsGroup* group)
282 : DistributionStat(name, group) {}
283 std::string ValueAsString() const override;
284 void Add(double value);
285};
286
287// Statistic on the distribution of a sequence of integers.
289 public:
290 explicit IntegerDistribution(absl::string_view name)
293 IntegerDistribution(absl::string_view name, StatsGroup* group)
294 : DistributionStat(name, group) {}
295 std::string ValueAsString() const override;
296 void Add(int64_t value);
297};
298
299// Helper classes to time a block of code and add the result to a
300// TimeDistribution. Calls StartTimer() on creation and
301// StopTimerAndAddElapsedTime() on destruction.
302//
303// There are three classes with the same interface:
304// * EnabledScopedTimeDistributionUpdater always collects the time stats of the
305// scope in which it is defined. This class is used for stats that are always
306// collected.
307// * ScopedTimeDistributionUpdater collects the time stats only when OR_STATS is
308// defined. This symbol should be used for collecting stats in places where
309// the overhead of collecting the stats may hurt the performance of the
310// algorithm.
311// * DisabledScopedTimeDistributionUpdater is used to implement
312// ScopedTimeDistributionUpdater when OR_STATS is not defined.
314 public:
315 // Note that this does not take ownership of the given stat.
317 : stat_(stat), also_update_(nullptr) {
318 stat->StartTimer();
319 }
320
321 // This type is neither copyable nor movable.
327 const double cycles = stat_->StopTimerAndAddElapsedTime();
328 if (also_update_ != nullptr) {
329 also_update_->AddTimeInCycles(cycles);
330 }
331 }
332
333 // Updates another TimeDistribution on destruction. This is useful to split
334 // a total time measurement in different categories:
335 //
336 // EnabledScopedTimeDistributionUpdater timer(&total_timer);
337 // ...
338 // switch (type) {
339 // case TypeA : timer.AlsoUpdate(&typeA_timer); break;
340 // case TypeB : timer.AlsoUpdate(&typeB_timer); break;
341 // }
342 void AlsoUpdate(TimeDistribution* also_update) { also_update_ = also_update; }
343
344 private:
345 TimeDistribution* stat_;
346 TimeDistribution* also_update_;
347};
348
360
369
370#ifdef OR_STATS
371
373#ifdef HAS_PERF_SUBSYSTEM
374using ScopedInstructionCounter = EnabledScopedInstructionCounter;
375#else // HAS_PERF_SUBSYSTEM
377#endif // HAS_PERF_SUBSYSTEM
378
379// Simple macro to be used by a client that want to execute costly operations
380// only if OR_STATS is defined.
381#define IF_STATS_ENABLED(instructions) instructions
382
383// Measures the time from this macro line to the end of the scope and adds it
384// to the distribution (from the given StatsGroup) with the same name as the
385// enclosing function.
386//
387// Note(user): This adds more extra overhead around the measured code compared
388// to defining your own TimeDistribution stat in your StatsGroup. About 80ns
389// per measurement compared to about 20ns (as of 2012-06, on my workstation).
390#define SCOPED_TIME_STAT(stats) \
391 operations_research::ScopedTimeDistributionUpdater scoped_time_stat( \
392 (stats)->LookupOrCreateTimeDistribution(__FUNCTION__))
393
394#ifdef HAS_PERF_SUBSYSTEM
395
396inline std::string RemoveOperationsResearchAndGlop(
397 const std::string& pretty_function) {
398 return strings::GlobalReplaceSubstrings(
399 pretty_function, {{"operations_research::", ""}, {"glop::", ""}});
400}
401
402#define SCOPED_INSTRUCTION_COUNT(time_limit) \
403 operations_research::ScopedInstructionCounter scoped_instruction_count( \
404 RemoveOperationsResearchAndGlop(__PRETTY_FUNCTION__), time_limit)
405
406#else // !HAS_PERF_SUBSYSTEM
407#define SCOPED_INSTRUCTION_COUNT(time_limit)
408#endif // HAS_PERF_SUBSYSTEM
409
410#else // !OR_STATS
411// If OR_STATS is not defined, we remove some instructions that may be time
412// consuming.
413
416
417#define IF_STATS_ENABLED(instructions)
418#define SCOPED_TIME_STAT(stats)
419#define SCOPED_INSTRUCTION_COUNT(time_limit)
420
421#endif // OR_STATS
422
423} // namespace operations_research
424
425#endif // OR_TOOLS_UTIL_STATS_H_
int64_t GetCycles() const
Definition timer.h:78
void Restart()
Definition timer.h:36
DisabledScopedInstructionCounter & operator=(const DisabledScopedInstructionCounter &)=delete
DisabledScopedInstructionCounter(const DisabledScopedInstructionCounter &)=delete
DisabledScopedTimeDistributionUpdater(TimeDistribution *stat)
Definition stats.h:351
DisabledScopedTimeDistributionUpdater(const DisabledScopedTimeDistributionUpdater &)=delete
This type is neither copyable nor movable.
void AlsoUpdate(TimeDistribution *also_update)
Definition stats.h:358
DisabledScopedTimeDistributionUpdater & operator=(const DisabledScopedTimeDistributionUpdater &)=delete
double Average() const
Get the average of the distribution or 0.0 if empty.
Definition stats.cc:174
bool WorthPrinting() const override
Is this stat worth printing? Usually false if nothing was measured.
Definition stats.h:181
double Sum() const override
Trivial statistics on all the values added so far.
Definition stats.h:187
void AddToDistribution(double value)
Adds a value to this sequence and updates the stats.
Definition stats.cc:156
std::string ValueAsString() const override=0
Implemented by the subclasses.
void Reset() override
Reset this statistic to the same state as if it was newly created.
Definition stats.cc:147
Statistic on the distribution of a sequence of doubles.
Definition stats.h:276
std::string ValueAsString() const override
Implemented by the subclasses.
Definition stats.cc:230
DoubleDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:281
DoubleDistribution(absl::string_view name)
Definition stats.h:278
EnabledScopedTimeDistributionUpdater(TimeDistribution *stat)
Definition stats.h:316
EnabledScopedTimeDistributionUpdater(const EnabledScopedTimeDistributionUpdater &)=delete
This type is neither copyable nor movable.
void AlsoUpdate(TimeDistribution *also_update)
Definition stats.h:342
EnabledScopedTimeDistributionUpdater & operator=(const EnabledScopedTimeDistributionUpdater &)=delete
Statistic on the distribution of a sequence of integers.
Definition stats.h:288
IntegerDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:293
std::string ValueAsString() const override
Implemented by the subclasses.
Definition stats.cc:239
IntegerDistribution(absl::string_view name)
Definition stats.h:290
Statistic on the distribution of a sequence of ratios, displayed as %.
Definition stats.h:265
std::string ValueAsString() const override
Implemented by the subclasses.
Definition stats.cc:222
RatioDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:269
RatioDistribution(absl::string_view name)
Definition stats.h:267
Base class for a statistic that can be pretty-printed.
Definition stats.h:90
std::string Name() const
Only used for display purposes.
Definition stats.h:99
virtual bool WorthPrinting() const =0
Is this stat worth printing? Usually false if nothing was measured.
virtual int Priority() const
Definition stats.h:108
virtual std::string ValueAsString() const =0
Prints information about this statistic.
Stat(absl::string_view name)
Definition stats.h:92
std::string StatString() const
Definition stats.cc:52
virtual double Sum() const
Definition stats.h:112
virtual void Reset()=0
Reset this statistic to the same state as if it was newly created.
Base class to print a nice summary of a group of statistics.
Definition stats.h:128
StatsGroup(const StatsGroup &)=delete
This type is neither copyable nor movable.
void SetPrintOrder(PrintOrder print_order)
Definition stats.h:154
StatsGroup & operator=(const StatsGroup &)=delete
TimeDistribution * LookupOrCreateTimeDistribution(std::string name)
Definition stats.cc:120
StatsGroup(absl::string_view name)
Definition stats.h:135
std::string StatString() const
Definition stats.cc:77
void Reset()
Calls Reset() on all the statistics registered with this group.
Definition stats.cc:58
void Register(Stat *stat)
Definition stats.cc:56
std::string ValueAsString() const override
Implemented by the subclasses.
Definition stats.cc:210
void AddTimeInSec(double seconds)
Adds a time in seconds to this distribution.
Definition stats.cc:199
TimeDistribution(absl::string_view name)
Definition stats.h:222
TimeDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:225
static double CyclesToSeconds(double num_cycles)
Definition stats.cc:181
void StartTimer()
Starts the timer in preparation of a StopTimerAndAddElapsedTime().
Definition stats.h:247
void AddTime(absl::Duration duration)
Definition stats.h:239
int Priority() const override
Time distributions have a high priority to be displayed first.
Definition stats.h:230
void AddTimeInCycles(double cycles)
Adds a time in CPU cycles to this distribution.
Definition stats.cc:205
const std::string name
A name for logging purposes.
int64_t value
In SWIG mode, we don't want anything besides these top-level includes.
std::string MemoryUsage()
Returns the current thread's total memory usage in an human-readable string.
Definition stats.cc:31