1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 Copyright (c) 2012-2021 The plumed team
3 (see the PEOPLE file at the root of the distribution for a list of names)
4
5 See http://www.plumed.org for more information.
6
7 This file is part of plumed, version 2.
8
9 plumed is free software: you can redistribute it and/or modify
10 it under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 plumed is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with plumed. If not, see <http://www.gnu.org/licenses/>.
21 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 #ifndef __PLUMED_tools_Stopwatch_h
23 #define __PLUMED_tools_Stopwatch_h
24
25 #include "Exception.h"
26 #include <string>
27 #include <unordered_map>
28 #include <iosfwd>
29 #include <chrono>
30
31 namespace PLMD {
32
33 /**
34 \ingroup TOOLBOX
35 Class implementing stopwatch to time execution.
36
37 Each instance of this class is a container which
38 can keep track of several named stopwatches at
39 the same time. Access to the stopwatches
40 is obtained using start(), stop(), pause() methods,
41 giving as a parameter the name of the specific stopwatch.
42 Also an empty string can be used (un-named stopwatch).
43 Finally, all the times can be logged using << operator
44
45 \verbatim
46 #include "Stopwatch.h"
47
48 int main(){
49 Stopwatch sw;
50 sw.start();
51
52 sw.start("initialization");
53 // do initialization ...
54 sw.stop("initialization");
55
56 for(int i=0;i<100;i++){
57 sw.start("loop");
58 // do calculation
59 sw.stop("loop");
60 }
61
62 sw.stop();
63 return 0;
64 }
65
66 \endverbatim
67
68 Using pause a stopwatch can be put on hold until
69 the next start:
70
71 \verbatim
72 #include "Stopwatch.h"
73
74 int main(){
75 Stopwatch sw;
76 sw.start();
77
78 sw.start("initialization");
79 // do initialization ...
80 sw.stop("initialization");
81
82 for(int i=0;i<100;i++){
83 sw.start("loop");
84 // do calculation
85 sw.pause("loop");
86 // here goes something that we do not want to include
87 sw.start("loop");
88 // do calculation
89 sw.stop("loop");
90 }
91
92 sw.stop();
93 return 0;
94 }
95
96 \endverbatim
97
98 Notice that as of PLUMED 2.5 it is possible to use a slightly modified
99 interface that allow for exception safety. In practice,
100 one can replace a pair of calls to Stopwatch::start() and Stopwatch::stop()
101 with a single call to Stopwatch::startStop(). This call will return an object
102 that, when goes out of scope, will stop the timer.
103
104 \notice The exception safety interace is highly recommended since it allows
105 to make sure that stopwatches are started and stopped consistently.
106
107 For instance the following
108 code
109 \verbatim
110 {
111 sw.start("A");
112 // any code
113 sw.stop("A");
114 }
115 \endverbatim
116 can be replaced with
117 \verbatim
118 {
119 auto sww=sw.startStop("A");
120 // any code
121
122 // stopwatch is stopped when sww goes out of scope
123 }
124 \endverbatim
125 Similarly, Stopwatch::startPause() can be used to replace a pair of
126 Stopwatch::start() and Stopwatch::pause().
127
128 The older syntax (explicitly calling `Stopwatch::start()` and `Stopwatch::pause()`) is still
129 allowed for backward compatibility.
130
131 Notice that the returned object is of type `Stopwatch::Handler`.
132 You might be willing to explicitly declare a `Stopwatch::Handler` (instead of using `auto`)
133 when you want to conditionally start the stopwatch. For instance:
134 \verbatim
135 {
136 Stopwatch::Handler handler;
137 if(you_want_to_time_this) handler=sw.startStop();
138 ... do something ...
139 }
140 // in case it was started, the stopwatch will stop here, at the end of the block
141 // in case it was not started, nothing will happen
142 \endverbatim
143
144 A `Stopwatch::Handler` can not be copied but it can be moved (it behaves like a unique_ptr).
145 Moving it explicitly allows one to transfer it to another `Stopwatch::Handler` with a different scope.
146 For instance, in case you want to conditionally stop the stopwatch you might use something like this:
147 \verbatim
148 {
149 Stopwatch::Handler handler;
150 if(you_want_to_time_this) handler=sw.startStop();
151 ... do something ...
152 if(you_want_to_stop_here) auto h2=std::move(handler);
153 // the previous instruction moves handler to h2 that is then destroyed, stopping the watch
154 // notice that if the stop was not started it will not stop.
155 ... do something else ...
156 }
157 // in case it is running, the stopwatch will stop here, at the end of the block
158 \endverbatim
159
160 Finally, notice that in order to write the timers on an output file when the
161 Stopwatch is destroyed, one can store a reference to a PLMD::Log by passing it
162 to the Stopwatch constructor.
163 This will make sure timers are written also in case of a premature end.
164 */
165
166 class Log;
167
168 /// Return an empty string.
169 /// Inline static so that it can store a static variable (for quicker access)
170 /// without adding a unique global symbol to a library including this header file.
StopwatchEmptyString()171 inline static const std::string & StopwatchEmptyString() noexcept {
172 const static std::string s;
173 return s;
174 }
175
176 class Stopwatch {
177
178 public:
179 /// Forward declaration
180 class Watch;
181 /// Auxiliary class for handling exception-safe start/pause and start/stop.
182 class Handler {
183 Watch* watch=nullptr;
184 /// stop (true) or pause (false).
185 /// might be changed to an enum if clearer.
186 bool stop=false;
187 /// Private constructor.
188 /// This is kept private to avoid misuse. Handler objects should
189 /// only be created using startPause() or startStop().
190 /// stop is required to know if the destructor should stop or pause the watch.
191 Handler(Watch* watch,bool stop);
192 /// Allows usage of private constructor
193 friend class Stopwatch;
194 public:
195 /// Default constructor
196 Handler() = default;
197 /// Default copy constructor is deleted (not copyable)
198 Handler(const Handler & handler) = delete;
199 /// Default copy assignment is deleted (not copyable)
200 Handler & operator=(const Handler & handler) = delete;
201 /// Move constructor.
202 Handler(Handler && handler) noexcept;
203 /// Move assignment.
204 Handler & operator=(Handler && handler) noexcept;
205 /// Destructor either stops or pauses the watch
206 ~Handler();
207 };
208
209 /// Class to store a single stopwatch.
210 /// Class Stopwatch contains a collection of them
211 class Watch {
212 /// Instant in time when Watch was started last time
213 std::chrono::time_point<std::chrono::high_resolution_clock> lastStart;
214 /// Total accumulated time, in nanoseconds
215 long long int total = 0;
216 /// Accumulated time for this lap, in nanoseconds
217 long long int lap = 0;
218 /// Slowest lap so far, in nanoseconds
219 long long int max = 0;
220 /// Fastest lap so far, in nanoseconds
221 long long int min = 0;
222 /// Total number of cycles
223 unsigned cycles = 0;
224 /// count how many times Watch was started (+1) or stopped/paused (-1).
225 unsigned running = 0;
226 enum class State {started, stopped, paused};
227 /// keep track of state
228 State state = State::stopped;
229 /// Allows access to internal data
230 friend class Stopwatch;
231 public:
232 /// start the watch
233 Watch & start();
234 /// stop the watch
235 Watch & stop();
236 /// pause the watch
237 Watch & pause();
238 /// returns a start-stop handler
239 Handler startStop();
240 /// returns a start-pause handler
241 Handler startPause();
242 };
243
244 private:
245
246 /// Pointer to a log file.
247 /// If set, the stopwatch is logged in its destructor.
248 Log*mylog=nullptr;
249
250 /// List of watches.
251 /// Each watch is labeled with a string.
252 std::unordered_map<std::string,Watch> watches;
253
254 /// Log over stream os.
255 std::ostream& log(std::ostream& os)const;
256
257 public:
258 // Constructor.
259 explicit Stopwatch() = default;
260 // Constructor.
261 // When destructing, stopwatch is logged.
262 // Make sure that log survives stopwatch. Typically, it should be declared earlier, in order
263 // to be destroyed later.
Stopwatch(Log & log)264 explicit Stopwatch(Log&log): mylog(&log) {}
265 // Destructor.
266 ~Stopwatch();
267 /// Start timer named "name"
268 Stopwatch& start(const std::string&name=StopwatchEmptyString());
269 /// Stop timer named "name"
270 Stopwatch& stop(const std::string&name=StopwatchEmptyString());
271 /// Pause timer named "name"
272 Stopwatch& pause(const std::string&name=StopwatchEmptyString());
273 /// Dump all timers on an ostream
274 friend std::ostream& operator<<(std::ostream&,const Stopwatch&);
275 /// Start with exception safety, then stop.
276 /// Starts the Stopwatch and returns an object that, when goes out of scope,
277 /// stops the watch. This allows Stopwatch to be started and stopped in
278 /// an exception safe manner.
279 Handler startStop(const std::string&name=StopwatchEmptyString());
280 /// Start with exception safety, then pause.
281 /// Starts the Stopwatch and returns an object that, when goes out of scope,
282 /// pauses the watch. This allows Stopwatch to be started and paused in
283 /// an exception safe manner.
284 Handler startPause(const std::string&name=StopwatchEmptyString());
285 };
286
287 inline
Handler(Watch * watch,bool stop)288 Stopwatch::Handler::Handler(Watch* watch,bool stop) :
289 watch(watch),
290 stop(stop)
291 {
292 watch->start();
293 }
294
295 inline
~Handler()296 Stopwatch::Handler::~Handler() {
297 if(watch) {
298 if(stop) watch->stop();
299 else watch->pause();
300 }
301 }
302
303 inline
start(const std::string & name)304 Stopwatch& Stopwatch::start(const std::string & name) {
305 watches[name].start();
306 return *this;
307 }
308
309 inline
stop(const std::string & name)310 Stopwatch& Stopwatch::stop(const std::string & name) {
311 watches[name].stop();
312 return *this;
313 }
314
315 inline
pause(const std::string & name)316 Stopwatch& Stopwatch::pause(const std::string & name) {
317 watches[name].pause();
318 return *this;
319 }
320
321 inline
startStop(const std::string & name)322 Stopwatch::Handler Stopwatch::startStop(const std::string&name) {
323 return watches[name].startStop();
324 }
325
326 inline
startPause(const std::string & name)327 Stopwatch::Handler Stopwatch::startPause(const std::string&name) {
328 return watches[name].startPause();
329 }
330
331 inline
Handler(Handler && handler)332 Stopwatch::Handler::Handler(Handler && handler) noexcept :
333 watch(handler.watch),
334 stop(handler.stop)
335 {
336 handler.watch=nullptr;
337 }
338
339 inline
340 Stopwatch::Handler & Stopwatch::Handler::operator=(Handler && handler) noexcept {
341 if(this!=&handler) {
342 if(watch) {
343 if(stop) {
344 try {
345 watch->stop();
catch(...)346 } catch(...) {
347 // this is to avoid problems with cppcheck, given than this method is declared as
348 // noexcept and stop might throw in case of an internal bug
349 std::terminate();
350 }
351 }
352 else watch->pause();
353 }
354 watch=handler.watch;
355 stop=handler.stop;
356 handler.watch=nullptr;
357 }
358 return *this;
359 }
360
361 inline
start()362 Stopwatch::Watch & Stopwatch::Watch::start() {
363 state=State::started;
364 running++;
365 lastStart=std::chrono::high_resolution_clock::now();
366 return *this;
367 }
368
369 inline
stop()370 Stopwatch::Watch & Stopwatch::Watch::stop() {
371 pause();
372 state=State::stopped;
373 cycles++;
374 total+=lap;
375 if(lap>max)max=lap;
376 if(min>lap || cycles==1)min=lap;
377 lap=0;
378 return *this;
379 }
380
381 inline
pause()382 Stopwatch::Watch & Stopwatch::Watch::pause() {
383 state=State::paused;
384 // In case of an internal bug (non matching start stop within the startStop or startPause interface)
385 // this assertion could fail in a destructor.
386 // If this happens, the program should crash immediately
387 plumed_assert(running>0) << "Non matching start/pause or start/stop commands in a Stopwatch";
388 running--;
389 // notice: with exception safety the following might be converted to a plain error.
390 // I leave it like this for now:
391 if(running!=0) return *this;
392 auto t=std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()-lastStart);
393 lap+=t.count();
394 return *this;
395 }
396
397 inline
startStop()398 Stopwatch::Handler Stopwatch::Watch::startStop() {
399 return Handler( this,true );
400 }
401
402 inline
startPause()403 Stopwatch::Handler Stopwatch::Watch::startPause() {
404 return Handler( this,false );
405 }
406
407
408 }
409
410
411 #endif
412