LCOV - code coverage report
Current view: top level - tools - Stopwatch.h (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 53 65 81.5 %
Date: 2024-10-18 14:00:25 Functions: 10 11 90.9 %

          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

Generated by: LCOV version 1.16