Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2012-2023 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 "Tools.h"
27 : #include <string>
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.
171 1331109 : inline static const std::string & StopwatchEmptyString() noexcept {
172 1331109 : const static std::string s;
173 1331109 : 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 13758 : 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 : /// last lap
228 : long long int lastLap = 0;
229 : /// keep track of state
230 : State state = State::stopped;
231 : /// Allows access to internal data
232 : friend class Stopwatch;
233 : public:
234 : /// start the watch
235 : Watch & start();
236 : /// stop the watch
237 : Watch & stop();
238 : /// pause the watch
239 : Watch & pause();
240 : /// returns a start-stop handler
241 : Handler startStop();
242 : /// returns a start-pause handler
243 : Handler startPause();
244 : /// returns the time for the last cycle
245 : long long int getLastCycle() noexcept;
246 : /// returns the total time
247 : long long int getTotal() noexcept;
248 : };
249 :
250 : private:
251 :
252 : /// Pointer to a log file.
253 : /// If set, the stopwatch is logged in its destructor.
254 : Log*mylog=nullptr;
255 :
256 : /// List of watches.
257 : /// Each watch is labeled with a string.
258 : Tools::FastStringUnorderedMap<Watch> watches;
259 :
260 : /// Log over stream os.
261 : std::ostream& log(std::ostream& os)const;
262 :
263 : public:
264 : // Constructor.
265 : explicit Stopwatch() = default;
266 : // Constructor.
267 : // When destructing, stopwatch is logged.
268 : // Make sure that log survives stopwatch. Typically, it should be declared earlier, in order
269 : // to be destroyed later.
270 806707 : explicit Stopwatch(Log&log): mylog(&log) {}
271 : // Destructor.
272 : ~Stopwatch();
273 : /// Deleted copy
274 : Stopwatch(const Stopwatch&) = delete;
275 : /// Deleted assignment
276 : Stopwatch& operator=(const Stopwatch&) = delete;
277 : /// Move constructor
278 : Stopwatch(Stopwatch&&) noexcept;
279 : /// Move assignment
280 : Stopwatch& operator=(Stopwatch&&) noexcept;
281 : /// Start timer named "name"
282 : Stopwatch& start(const std::string_view&name=StopwatchEmptyString());
283 : /// Stop timer named "name"
284 : Stopwatch& stop(const std::string_view&name=StopwatchEmptyString());
285 : /// Pause timer named "name"
286 : Stopwatch& pause(const std::string_view&name=StopwatchEmptyString());
287 : /// Dump all timers on an ostream
288 : friend std::ostream& operator<<(std::ostream&,const Stopwatch&);
289 : /// Start with exception safety, then stop.
290 : /// Starts the Stopwatch and returns an object that, when goes out of scope,
291 : /// stops the watch. This allows Stopwatch to be started and stopped in
292 : /// an exception safe manner.
293 : Handler startStop(const std::string_view&name=StopwatchEmptyString());
294 : /// Start with exception safety, then pause.
295 : /// Starts the Stopwatch and returns an object that, when goes out of scope,
296 : /// pauses the watch. This allows Stopwatch to be started and paused in
297 : /// an exception safe manner.
298 : Handler startPause(const std::string_view&name=StopwatchEmptyString());
299 : /// Return the last completed cycle
300 : long long int getLastCycle(const std::string_view&name=StopwatchEmptyString());
301 : /// returns the total time
302 : long long int getTotal(const std::string_view&name=StopwatchEmptyString());
303 : };
304 :
305 : inline
306 : Stopwatch::Handler::Handler(Watch* watch,bool stop) :
307 3031371 : watch(watch),
308 3031371 : stop(stop)
309 : {
310 : watch->start();
311 : }
312 :
313 : inline
314 8554593 : Stopwatch::Handler::~Handler() {
315 8554593 : if(watch) {
316 3031371 : if(stop) watch->stop();
317 1331107 : else watch->pause();
318 : }
319 8554593 : }
320 :
321 : inline
322 8 : Stopwatch& Stopwatch::start(const std::string_view & name) {
323 8 : watches[name].start();
324 8 : return *this;
325 : }
326 :
327 : inline
328 4 : Stopwatch& Stopwatch::stop(const std::string_view & name) {
329 4 : watches[name].stop();
330 4 : return *this;
331 : }
332 :
333 : inline
334 1 : Stopwatch& Stopwatch::pause(const std::string_view & name) {
335 1 : watches[name].pause();
336 1 : return *this;
337 : }
338 :
339 : inline
340 1700264 : Stopwatch::Handler Stopwatch::startStop(const std::string_view&name) {
341 1700264 : return watches[name].startStop();
342 : }
343 :
344 : inline
345 1331107 : Stopwatch::Handler Stopwatch::startPause(const std::string_view&name) {
346 1331107 : return watches[name].startPause();
347 : }
348 :
349 : inline
350 : long long int Stopwatch::getLastCycle(const std::string_view&name) {
351 0 : return watches[name].getLastCycle();
352 : }
353 :
354 : inline
355 : long long int Stopwatch::getTotal(const std::string_view&name) {
356 : return watches[name].getTotal();
357 : }
358 :
359 : inline
360 : Stopwatch::Handler::Handler(Handler && handler) noexcept :
361 : watch(handler.watch),
362 : stop(handler.stop)
363 : {
364 : handler.watch=nullptr;
365 : }
366 :
367 : inline
368 5075 : Stopwatch::Handler & Stopwatch::Handler::operator=(Handler && handler) noexcept {
369 5075 : if(this!=&handler) {
370 5075 : if(watch) {
371 : try {
372 0 : if(stop) watch->stop();
373 0 : else watch->pause();
374 0 : } catch(...) {
375 : // this is to avoid problems with cppcheck, given than this method is declared as
376 : // noexcept and stop and pause might throw in case of an internal bug
377 0 : std::terminate();
378 : }
379 : }
380 5075 : watch=handler.watch;
381 5075 : stop=handler.stop;
382 5075 : handler.watch=nullptr;
383 : }
384 5075 : return *this;
385 : }
386 :
387 : inline
388 : Stopwatch::Watch & Stopwatch::Watch::start() {
389 3032668 : state=State::started;
390 3032668 : running++;
391 3032668 : lastStart=std::chrono::high_resolution_clock::now();
392 : return *this;
393 : }
394 :
395 : inline
396 1701557 : Stopwatch::Watch & Stopwatch::Watch::stop() {
397 1701557 : pause();
398 1701557 : state=State::stopped;
399 1701557 : cycles++;
400 1701557 : total+=lap;
401 1701557 : if(lap>max)max=lap;
402 1701557 : if(min>lap || cycles==1)min=lap;
403 1701557 : lastLap=lap;
404 1701557 : lap=0;
405 1701557 : return *this;
406 : }
407 :
408 : inline
409 3032511 : Stopwatch::Watch & Stopwatch::Watch::pause() {
410 3032511 : state=State::paused;
411 : // In case of an internal bug (non matching start stop within the startStop or startPause interface)
412 : // this assertion could fail in a destructor.
413 : // If this happens, the program should crash immediately
414 3032511 : plumed_assert(running>0) << "Non matching start/pause or start/stop commands in a Stopwatch";
415 3032511 : running--;
416 : // notice: with exception safety the following might be converted to a plain error.
417 : // I leave it like this for now:
418 3032511 : if(running!=0) return *this;
419 3032460 : auto t=std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()-lastStart);
420 3032460 : lap+=t.count();
421 3032460 : return *this;
422 : }
423 :
424 : inline
425 : Stopwatch::Handler Stopwatch::Watch::startStop() {
426 : return Handler( this,true );
427 : }
428 :
429 : inline
430 : Stopwatch::Handler Stopwatch::Watch::startPause() {
431 : return Handler( this,false );
432 : }
433 :
434 : inline
435 : long long int Stopwatch::Watch::getLastCycle() noexcept {
436 0 : return lastLap;
437 : }
438 :
439 : inline
440 : long long int Stopwatch::Watch::getTotal() noexcept {
441 : return total;
442 : }
443 :
444 : inline
445 : Stopwatch::Stopwatch(Stopwatch&& other) noexcept:
446 : mylog(other.mylog),
447 : watches(std::move(other.watches))
448 : {
449 : other.mylog=nullptr;
450 : }
451 :
452 : inline
453 0 : Stopwatch& Stopwatch::operator=(Stopwatch&& other) noexcept {
454 0 : if(this!=&other) {
455 0 : mylog=other.mylog;
456 0 : watches=std::move(other.watches);
457 0 : other.mylog=nullptr;
458 : }
459 0 : return *this;
460 : }
461 :
462 :
463 : }
464 :
465 :
466 : #endif
|