LCOV - code coverage report
Current view: top level - tools - Stopwatch.h (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 51 55 92.7 %
Date: 2024-10-11 08:09:47 Functions: 10 10 100.0 %

          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

Generated by: LCOV version 1.15