Google OR-Tools v9.15
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-2025 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 ORTOOLS_UTIL_STATS_H_
69#define ORTOOLS_UTIL_STATS_H_
70
71#include <cstdint>
72#include <memory>
73#include <string>
74#include <vector>
75
76#include "absl/container/flat_hash_map.h"
77#include "absl/strings/string_view.h"
78#include "absl/time/time.h"
79#include "ortools/base/timer.h"
80
81namespace operations_research {
82
83// Returns the current thread's total memory usage in an human-readable string.
84std::string MemoryUsage();
85
86// Forward declaration.
87class StatsGroup;
89
90// Base class for a statistic that can be pretty-printed.
91class Stat {
92 public:
93 explicit Stat(absl::string_view name) : name_(name) {}
94
95 // Also add this stat to the given group.
96 Stat(absl::string_view name, StatsGroup* group);
97 virtual ~Stat() {}
98
99 // Only used for display purposes.
100 std::string Name() const { return name_; }
101
102 // Returns a human-readable formatted line of the form "name:
103 // ValueAsString()".
104 std::string StatString() const;
105
106 // At display, stats are displayed by decreasing priority, then decreasing
107 // Sum(), then alphabetical order.
108 // Used to group the stats per category (timing, ratio, etc..,).
109 virtual int Priority() const { return 0; }
110
111 // By default return 0 for the sum. This makes it possible to sort stats by
112 // decreasing total time.
113 virtual double Sum() const { return 0; }
114
115 // Prints information about this statistic.
116 virtual std::string ValueAsString() const = 0;
117
118 // Is this stat worth printing? Usually false if nothing was measured.
119 virtual bool WorthPrinting() const = 0;
120
121 // Reset this statistic to the same state as if it was newly created.
122 virtual void Reset() = 0;
123
124 private:
125 const std::string name_;
126};
127
128// Base class to print a nice summary of a group of statistics.
130 public:
135
136 explicit StatsGroup(absl::string_view name)
137 : name_(name), stats_(), time_distributions_() {}
138
139 // This type is neither copyable nor movable.
140 StatsGroup(const StatsGroup&) = delete;
141 StatsGroup& operator=(const StatsGroup&) = delete;
142 ~StatsGroup() = default;
143
144 // Registers a Stat, which will appear in the string returned by StatString().
145 // The Stat object must live as long as this StatsGroup.
146 void Register(Stat* stat);
147
148 // Returns this group name, followed by one line per Stat registered with this
149 // group (this includes the ones created by LookupOrCreateTimeDistribution()).
150 // Note that only the stats WorthPrinting() are printed.
151 std::string StatString() const;
152
153 // Changes the print ordering (will affect the order in which the stats
154 // registered with this group are printed via StatString()).
155 void SetPrintOrder(PrintOrder print_order) { print_order_ = print_order; }
156
157 // Returns and if needed creates and registers a TimeDistribution with the
158 // given name. Note that this involve a hash map lookup and is thus slower
159 // than directly accessing a TimeDistribution variable.
160 TimeDistribution* LookupOrCreateTimeDistribution(absl::string_view name);
161
162 // Calls Reset() on all the statistics registered with this group.
163 void Reset();
164
165 private:
166 std::string name_;
168 std::vector<Stat*> stats_;
169 absl::flat_hash_map<std::string, std::unique_ptr<TimeDistribution>>
170 time_distributions_;
171};
172
173// Base class to track and compute statistics about the distribution of a
174// sequence of double. We provide a few sub-classes below that differ in the way
175// the values are added to the sequence and in the way the stats are printed.
176class DistributionStat : public Stat {
177 public:
178 explicit DistributionStat(absl::string_view name);
180 DistributionStat(absl::string_view name, StatsGroup* group);
181 ~DistributionStat() override {}
182 void Reset() override;
183 bool WorthPrinting() const override { return num_ != 0; }
184
185 // Implemented by the subclasses.
186 std::string ValueAsString() const override = 0;
187
188 // Trivial statistics on all the values added so far.
189 double Sum() const override { return sum_; }
190 double Max() const { return max_; }
191 double Min() const { return min_; }
192 int64_t Num() const { return num_; }
193
194 // Get the average of the distribution or 0.0 if empty.
195 double Average() const;
196
197 // Get the standard deviation of the distribution or 0.0 if empty.
198 // We use the on-line algorithm of Welford described at
199 // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
200 // TODO(user): We could also use on top the Kahan summation algorithm to be
201 // even more precise but a bit slower too.
202 double StdDeviation() const;
203
204 protected:
205 // Adds a value to this sequence and updates the stats.
206 void AddToDistribution(double value);
207 double sum_;
208 double average_;
210 double min_;
211 double max_;
212 int64_t num_;
213};
214
215// Statistic on the distribution of a sequence of running times.
216// Also provides some facility to measure such time with the CPU cycle counter.
217//
218// TODO(user): Since we inherit from DistributionStat, we currently store the
219// sum of CPU cycles as a double internally. A better option is to use int64_t
220// because with the 53 bits of precision of a double, we will run into an issue
221// if the sum of times reaches 52 days for a 2GHz processor.
223 public:
224 explicit TimeDistribution(absl::string_view name)
225 : DistributionStat(name), timer_() {}
227 TimeDistribution(absl::string_view name, StatsGroup* group)
228 : DistributionStat(name, group), timer_() {}
229 std::string ValueAsString() const override;
230
231 // Time distributions have a high priority to be displayed first.
232 int Priority() const override { return 100; }
233
234 // Internally the TimeDistribution stores CPU cycles (to do a bit less work
235 // on each StopTimerAndAddElapsedTime()). Use this function to convert
236 // all the statistics of DistributionStat into seconds.
237 static double CyclesToSeconds(double num_cycles);
238
239 // Adds a time in seconds to this distribution.
240 void AddTimeInSec(double seconds);
241 void AddTime(absl::Duration duration) {
242 AddTimeInSec(absl::ToDoubleSeconds(duration));
243 }
244
245 // Adds a time in CPU cycles to this distribution.
246 void AddTimeInCycles(double cycles);
247
248 // Starts the timer in preparation of a StopTimerAndAddElapsedTime().
249 inline void StartTimer() { timer_.Restart(); }
250
251 // Adds the elapsed time since the last StartTimer() to the distribution and
252 // returns this time in CPU cycles.
254 const double cycles = static_cast<double>(timer_.GetCycles());
255 AddToDistribution(cycles);
256 return cycles;
257 }
258
259 private:
260 // Converts and prints a number of cycles in an human readable way using the
261 // proper time unit depending on the value (ns, us, ms, s, m or h).
262 static std::string PrintCyclesAsTime(double cycles);
263 CycleTimer timer_;
264};
265
266// Statistic on the distribution of a sequence of ratios, displayed as %.
268 public:
269 explicit RatioDistribution(absl::string_view name) : DistributionStat(name) {}
271 RatioDistribution(absl::string_view name, StatsGroup* group)
272 : DistributionStat(name, group) {}
273 std::string ValueAsString() const override;
274 void Add(double value);
275};
276
277// Statistic on the distribution of a sequence of doubles.
279 public:
280 explicit DoubleDistribution(absl::string_view name)
281 : DistributionStat(name) {}
283 DoubleDistribution(absl::string_view name, StatsGroup* group)
284 : DistributionStat(name, group) {}
285 std::string ValueAsString() const override;
286 void Add(double value);
287};
288
289// Statistic on the distribution of a sequence of integers.
291 public:
292 explicit IntegerDistribution(absl::string_view name)
293 : DistributionStat(name) {}
295 IntegerDistribution(absl::string_view name, StatsGroup* group)
296 : DistributionStat(name, group) {}
297 std::string ValueAsString() const override;
298 void Add(int64_t value);
299};
300
301// Helper classes to time a block of code and add the result to a
302// TimeDistribution. Calls StartTimer() on creation and
303// StopTimerAndAddElapsedTime() on destruction.
304//
305// There are three classes with the same interface:
306// * EnabledScopedTimeDistributionUpdater always collects the time stats of the
307// scope in which it is defined. This class is used for stats that are always
308// collected.
309// * ScopedTimeDistributionUpdater collects the time stats only when OR_STATS is
310// defined. This symbol should be used for collecting stats in places where
311// the overhead of collecting the stats may hurt the performance of the
312// algorithm.
313// * DisabledScopedTimeDistributionUpdater is used to implement
314// ScopedTimeDistributionUpdater when OR_STATS is not defined.
316 public:
317 // Note that this does not take ownership of the given stat.
319 : stat_(stat), also_update_(nullptr) {
320 stat->StartTimer();
321 }
322
323 // This type is neither copyable nor movable.
329 const double cycles = stat_->StopTimerAndAddElapsedTime();
330 if (also_update_ != nullptr) {
331 also_update_->AddTimeInCycles(cycles);
332 }
333 }
334
335 // Updates another TimeDistribution on destruction. This is useful to split
336 // a total time measurement in different categories:
337 //
338 // EnabledScopedTimeDistributionUpdater timer(&total_timer);
339 // ...
340 // switch (type) {
341 // case TypeA : timer.AlsoUpdate(&typeA_timer); break;
342 // case TypeB : timer.AlsoUpdate(&typeB_timer); break;
343 // }
344 void AlsoUpdate(TimeDistribution* also_update) { also_update_ = also_update; }
345
346 private:
347 TimeDistribution* stat_;
348 TimeDistribution* also_update_;
349};
350
362
371
372// Measures the time from this macro line to the end of the scope and adds it
373// to the distribution (from the given StatsGroup) with the same name as the
374// enclosing function.
375//
376// Note(user): This adds more extra overhead around the measured code compared
377// to defining your own TimeDistribution stat in your StatsGroup. About 80ns
378// per measurement compared to about 20ns (as of 2012-06, on my workstation).
380 public:
382 absl::string_view function_name)
383 : scoped_time_stat_(
384 stats->LookupOrCreateTimeDistribution(function_name)) {}
389
390 private:
392};
393
394#ifdef OR_STATS
395
398
399// Simple macro to be used by a client that want to execute costly operations
400// only if OR_STATS is defined.
401#define IF_STATS_ENABLED(instructions) instructions
402
403#else // !OR_STATS
404// If OR_STATS is not defined, we remove some instructions that may be time
405// consuming.
406
409
410// Defining it that way makes sure that the compiler still sees the code and
411// checks that the syntax & types are valid.
412#define IF_STATS_ENABLED(instructions) \
413 if constexpr (false) { \
414 instructions; \
415 }
416
417#endif // OR_STATS
418
419#define SCOPED_TIME_STAT(stats) \
420 operations_research::ScopedTimeStats scoped_time_stat(stats, __FUNCTION__);
421
422#define SCOPED_INSTRUCTION_COUNT(time_limit)
423
424} // namespace operations_research
425
426#endif // ORTOOLS_UTIL_STATS_H_
DisabledScopedTimeDistributionUpdater(const DisabledScopedTimeDistributionUpdater &)=delete
DisabledScopedTimeDistributionUpdater & operator=(const DisabledScopedTimeDistributionUpdater &)=delete
DisabledScopedTimeStats(StatsGroup *, const char *)
Definition stats.h:365
DisabledScopedTimeStats & operator=(DisabledScopedTimeStats &&)=delete
DisabledScopedTimeStats(const DisabledScopedTimeStats &)=delete
DisabledScopedTimeStats(DisabledScopedTimeStats &&)=delete
DisabledScopedTimeStats & operator=(const DisabledScopedTimeStats &)=delete
bool WorthPrinting() const override
Definition stats.h:183
double Sum() const override
Definition stats.h:189
void AddToDistribution(double value)
Definition stats.cc:173
std::string ValueAsString() const override=0
DistributionStat(absl::string_view name)
Definition stats.cc:146
std::string ValueAsString() const override
Definition stats.cc:247
DoubleDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:283
DoubleDistribution(absl::string_view name)
Definition stats.h:280
EnabledScopedTimeDistributionUpdater(TimeDistribution *stat)
Definition stats.h:318
EnabledScopedTimeDistributionUpdater(const EnabledScopedTimeDistributionUpdater &)=delete
void AlsoUpdate(TimeDistribution *also_update)
Definition stats.h:344
EnabledScopedTimeDistributionUpdater & operator=(const EnabledScopedTimeDistributionUpdater &)=delete
EnabledScopedTimeStats & operator=(const EnabledScopedTimeStats &)=delete
EnabledScopedTimeStats(StatsGroup *stats, absl::string_view function_name)
Definition stats.h:381
EnabledScopedTimeStats(const EnabledScopedTimeStats &)=delete
EnabledScopedTimeStats & operator=(EnabledScopedTimeStats &&)=delete
EnabledScopedTimeStats(EnabledScopedTimeStats &&)=delete
IntegerDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:295
std::string ValueAsString() const override
Definition stats.cc:256
IntegerDistribution(absl::string_view name)
Definition stats.h:292
std::string ValueAsString() const override
Definition stats.cc:239
RatioDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:271
RatioDistribution(absl::string_view name)
Definition stats.h:269
std::string Name() const
Definition stats.h:100
virtual bool WorthPrinting() const =0
virtual int Priority() const
Definition stats.h:109
virtual std::string ValueAsString() const =0
Stat(absl::string_view name)
Definition stats.h:93
std::string StatString() const
Definition stats.cc:53
virtual double Sum() const
Definition stats.h:113
virtual void Reset()=0
StatsGroup(const StatsGroup &)=delete
void SetPrintOrder(PrintOrder print_order)
Definition stats.h:155
StatsGroup & operator=(const StatsGroup &)=delete
TimeDistribution * LookupOrCreateTimeDistribution(absl::string_view name)
Definition stats.cc:136
StatsGroup(absl::string_view name)
Definition stats.h:136
std::string StatString() const
Definition stats.cc:95
void Register(Stat *stat)
Definition stats.cc:55
std::string ValueAsString() const override
Definition stats.cc:227
void AddTimeInSec(double seconds)
Definition stats.cc:216
TimeDistribution(absl::string_view name)
Definition stats.h:224
TimeDistribution(absl::string_view name, StatsGroup *group)
Definition stats.h:227
static double CyclesToSeconds(double num_cycles)
Definition stats.cc:198
void AddTime(absl::Duration duration)
Definition stats.h:241
int Priority() const override
Definition stats.h:232
void AddTimeInCycles(double cycles)
Definition stats.cc:222
OR-Tools root namespace.
DisabledScopedTimeDistributionUpdater ScopedTimeDistributionUpdater
Definition stats.h:407
DisabledScopedTimeStats ScopedTimeStats
Definition stats.h:408
std::string MemoryUsage()
Definition stats.cc:32