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 <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.
171 1043018 : inline static const std::string & StopwatchEmptyString() noexcept {
172 1043018 : const static std::string s;
173 1043018 : 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 10042 : 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.
264 404404 : 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
288 : Stopwatch::Handler::Handler(Watch* watch,bool stop) :
289 2522418 : watch(watch),
290 2522418 : stop(stop)
291 : {
292 : watch->start();
293 : }
294 :
295 : inline
296 6051210 : Stopwatch::Handler::~Handler() {
297 6051210 : if(watch) {
298 2522418 : if(stop) watch->stop();
299 1040740 : else watch->pause();
300 : }
301 6051210 : }
302 :
303 : inline
304 1146 : Stopwatch& Stopwatch::start(const std::string & name) {
305 : watches[name].start();
306 1146 : return *this;
307 : }
308 :
309 : inline
310 573 : Stopwatch& Stopwatch::stop(const std::string & name) {
311 573 : watches[name].stop();
312 573 : return *this;
313 : }
314 :
315 : inline
316 570 : Stopwatch& Stopwatch::pause(const std::string & name) {
317 570 : watches[name].pause();
318 570 : return *this;
319 : }
320 :
321 : inline
322 1481678 : Stopwatch::Handler Stopwatch::startStop(const std::string&name) {
323 1481678 : return watches[name].startStop();
324 : }
325 :
326 : inline
327 1040740 : Stopwatch::Handler Stopwatch::startPause(const std::string&name) {
328 1040740 : return watches[name].startPause();
329 : }
330 :
331 : inline
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 3493 : Stopwatch::Handler & Stopwatch::Handler::operator=(Handler && handler) noexcept {
341 3493 : if(this!=&handler) {
342 3493 : if(watch) {
343 : try {
344 0 : if(stop) watch->stop();
345 0 : else watch->pause();
346 0 : } catch(...) {
347 : // this is to avoid problems with cppcheck, given than this method is declared as
348 : // noexcept and stop and pause might throw in case of an internal bug
349 0 : std::terminate();
350 : }
351 : }
352 3493 : watch=handler.watch;
353 3493 : stop=handler.stop;
354 3493 : handler.watch=nullptr;
355 : }
356 3493 : return *this;
357 : }
358 :
359 : inline
360 : Stopwatch::Watch & Stopwatch::Watch::start() {
361 2524478 : state=State::started;
362 2524478 : running++;
363 2524478 : lastStart=std::chrono::high_resolution_clock::now();
364 : return *this;
365 : }
366 :
367 : inline
368 1483165 : Stopwatch::Watch & Stopwatch::Watch::stop() {
369 1483165 : pause();
370 1483165 : state=State::stopped;
371 1483165 : cycles++;
372 1483165 : total+=lap;
373 1483165 : if(lap>max)max=lap;
374 1483165 : if(min>lap || cycles==1)min=lap;
375 1483165 : lap=0;
376 1483165 : return *this;
377 : }
378 :
379 : inline
380 2524352 : Stopwatch::Watch & Stopwatch::Watch::pause() {
381 2524352 : state=State::paused;
382 : // In case of an internal bug (non matching start stop within the startStop or startPause interface)
383 : // this assertion could fail in a destructor.
384 : // If this happens, the program should crash immediately
385 2524352 : plumed_assert(running>0) << "Non matching start/pause or start/stop commands in a Stopwatch";
386 2524352 : running--;
387 : // notice: with exception safety the following might be converted to a plain error.
388 : // I leave it like this for now:
389 2524352 : if(running!=0) return *this;
390 2524301 : auto t=std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()-lastStart);
391 2524301 : lap+=t.count();
392 2524301 : return *this;
393 : }
394 :
395 : inline
396 : Stopwatch::Handler Stopwatch::Watch::startStop() {
397 : return Handler( this,true );
398 : }
399 :
400 : inline
401 : Stopwatch::Handler Stopwatch::Watch::startPause() {
402 : return Handler( this,false );
403 : }
404 :
405 :
406 : }
407 :
408 :
409 : #endif
|