Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2012-2019 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 pratice,
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 : class Stopwatch {
169 : /// Simple function returning an empty string.
170 : /// Used to simplify Stopwatch interface.
171 520191 : static const std::string & emptyString() {
172 522137 : static std::string s;
173 520191 : return s;
174 : }
175 :
176 : public:
177 : /// Forward declaration
178 : class Watch;
179 : /// Auxiliary class for handling exception-safe start/pause and start/stop.
180 : class Handler {
181 : Watch* watch=nullptr;
182 : /// stop (true) or pause (false).
183 : /// might be changed to an enum if clearer.
184 : bool stop=false;
185 : /// Private constructor.
186 : /// This is kept private to avoid misuse. Handler objects should
187 : /// only be created using startPause() or startStop().
188 : /// stop is required to know if the destructor should stop or pause the watch.
189 : Handler(Watch* watch,bool stop);
190 : /// Allows usage of private constructor
191 : friend class Stopwatch;
192 : public:
193 : /// Default constructor
194 : Handler() = default;
195 : /// Default copy constructor is deleted (not copyable)
196 : Handler(const Handler & handler) = delete;
197 : /// Default copy assignment is deleted (not copyable)
198 : Handler & operator=(const Handler & handler) = delete;
199 : /// Move constructor.
200 : Handler(Handler && handler) noexcept;
201 : /// Move assignemnt.
202 : Handler & operator=(Handler && handler) noexcept;
203 : /// Destructor either stops or pauses the watch
204 : ~Handler();
205 : };
206 :
207 : /// Class to store a single stopwatch.
208 : /// Class Stopwatch contains a collection of them
209 7687 : class Watch {
210 : /// Instant in time when Watch was started last time
211 : std::chrono::time_point<std::chrono::high_resolution_clock> lastStart;
212 : /// Total accumulated time, in nanoseconds
213 : long long int total = 0;
214 : /// Accumulated time for this lap, in nanoseconds
215 : long long int lap = 0;
216 : /// Slowest lap so far, in nanoseconds
217 : long long int max = 0;
218 : /// Fastest lap so far, in nanoseconds
219 : long long int min = 0;
220 : /// Total number of cycles
221 : unsigned cycles = 0;
222 : /// count how many times Watch was started (+1) or stopped/paused (-1).
223 : unsigned running = 0;
224 : enum class State {started, stopped, paused};
225 : /// keep track of state
226 : State state = State::stopped;
227 : /// Allows access to internal data
228 : friend class Stopwatch;
229 : public:
230 : /// start the watch
231 : Watch & start();
232 : /// stop the watch
233 : Watch & stop();
234 : /// pause the watch
235 : Watch & pause();
236 : /// returns a start-stop handler
237 : Handler startStop();
238 : /// returns a start-pause handler
239 : Handler startPause();
240 : };
241 :
242 : private:
243 :
244 : /// Pointer to a log file.
245 : /// If set, the stopwatch is logged in its destructor.
246 : Log*mylog=nullptr;
247 :
248 : /// List of watches.
249 : /// Each watch is labeled with a string.
250 : std::unordered_map<std::string,Watch> watches;
251 :
252 : /// Log over stream os.
253 : std::ostream& log(std::ostream& os)const;
254 :
255 : public:
256 : // Constructor.
257 1124 : explicit Stopwatch() = default;
258 : // Constructor.
259 : // When destructing, stopwatch is logged.
260 : // Make sure that log survives stopwatch. Typically, it should be declared earlier, in order
261 : // to be destroyed later.
262 5466 : explicit Stopwatch(Log&log): mylog(&log) {}
263 : // Destructor.
264 : ~Stopwatch();
265 : /// Start timer named "name"
266 2202 : Stopwatch& start(const std::string&name=emptyString());
267 : /// Stop timer named "name"
268 : Stopwatch& stop(const std::string&name=emptyString());
269 : /// Pause timer named "name"
270 : Stopwatch& pause(const std::string&name=emptyString());
271 : /// Dump all timers on an ostream
272 : friend std::ostream& operator<<(std::ostream&,const Stopwatch&);
273 : /// Start with exception safety, then stop.
274 : /// Starts the Stopwatch and returns an object that, when goes out of scope,
275 : /// stops the watch. This allows Stopwatch to be started and stopped in
276 : /// an exception safe manner.
277 517989 : Handler startStop(const std::string&name=emptyString());
278 : /// Start with exception safety, then pause.
279 : /// Starts the Stopwatch and returns an object that, when goes out of scope,
280 : /// pauses the watch. This allows Stopwatch to be started and paused in
281 : /// an exception safe manner.
282 : Handler startPause(const std::string&name=emptyString());
283 : };
284 :
285 : inline
286 : Stopwatch::Handler::Handler(Watch* watch,bool stop) :
287 : watch(watch),
288 1026755 : stop(stop)
289 : {
290 : watch->start();
291 : }
292 :
293 : inline
294 2020810 : Stopwatch::Handler::~Handler() {
295 2020810 : if(watch) {
296 1026755 : if(stop) watch->stop();
297 517989 : else watch->pause();
298 : }
299 2020810 : }
300 :
301 : inline
302 1108 : Stopwatch& Stopwatch::start(const std::string & name) {
303 : watches[name].start();
304 1108 : return *this;
305 : }
306 :
307 : inline
308 : Stopwatch& Stopwatch::stop(const std::string & name) {
309 554 : watches[name].stop();
310 : return *this;
311 : }
312 :
313 : inline
314 : Stopwatch& Stopwatch::pause(const std::string & name) {
315 551 : watches[name].pause();
316 : return *this;
317 : }
318 :
319 : inline
320 508766 : Stopwatch::Handler Stopwatch::startStop(const std::string&name) {
321 508766 : return watches[name].startStop();
322 : }
323 :
324 : inline
325 517989 : Stopwatch::Handler Stopwatch::startPause(const std::string&name) {
326 517989 : return watches[name].startPause();
327 : }
328 :
329 : inline
330 : Stopwatch::Handler::Handler(Handler && handler) noexcept :
331 : watch(handler.watch),
332 : stop(handler.stop)
333 : {
334 : handler.watch=nullptr;
335 : }
336 :
337 : inline
338 3493 : Stopwatch::Handler & Stopwatch::Handler::operator=(Handler && handler) noexcept {
339 3493 : if(this!=&handler) {
340 3493 : if(watch) {
341 0 : if(stop) {
342 : try {
343 0 : watch->stop();
344 0 : } catch(...) {
345 : // this is to avoid problems with cppcheck, given than this method is declared as
346 : // noexcept and stop might throw in case of an internal bug
347 0 : std::terminate();
348 : }
349 : }
350 0 : else watch->pause();
351 : }
352 : // cppcheck complains about this:
353 : // cppcheck-suppress uselessAssignmentPtrArg
354 3493 : watch=handler.watch;
355 3493 : stop=handler.stop;
356 3493 : handler.watch=nullptr;
357 : }
358 3493 : return *this;
359 : }
360 :
361 : inline
362 : Stopwatch::Watch & Stopwatch::Watch::start() {
363 1028634 : state=State::started;
364 1028634 : running++;
365 1028634 : lastStart=std::chrono::high_resolution_clock::now();
366 : return *this;
367 : }
368 :
369 : inline
370 510091 : Stopwatch::Watch & Stopwatch::Watch::stop() {
371 510091 : pause();
372 510091 : state=State::stopped;
373 510091 : cycles++;
374 510091 : total+=lap;
375 510091 : if(lap>max)max=lap;
376 510091 : if(min>lap || cycles==1)min=lap;
377 510091 : lap=0;
378 510091 : return *this;
379 : }
380 :
381 : inline
382 1028546 : Stopwatch::Watch & Stopwatch::Watch::pause() {
383 1028546 : 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 1028546 : plumed_assert(running>0) << "Non matching start/pause or start/stop commands in a Stopwatch";
388 1028546 : running--;
389 : // notice: with exception safety the following might be converted to a plain error.
390 : // I leave it like this for now:
391 1028546 : if(running!=0) return *this;
392 2056990 : auto t=std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now()-lastStart);
393 1028495 : lap+=t.count();
394 1028495 : return *this;
395 : }
396 :
397 : inline
398 : Stopwatch::Handler Stopwatch::Watch::startStop() {
399 : return Handler( this,true );
400 : }
401 :
402 : inline
403 : Stopwatch::Handler Stopwatch::Watch::startPause() {
404 : return Handler( this,false );
405 : }
406 :
407 :
408 : }
409 :
410 :
411 : #endif
|