Home | History | Annotate | Line # | Download | only in datetime
      1 // Written in the D programming language
      2 
      3 /++
      4     Module containing some basic benchmarking and timing functionality.
      5 
      6     For convenience, this module publicly imports $(MREF core,time).
      7 
      8 $(SCRIPT inhibitQuickIndex = 1;)
      9 $(DIVC quickindex,
     10 $(BOOKTABLE,
     11 $(TR $(TH Category) $(TH Functions))
     12 $(TR $(TD Main functionality) $(TD
     13     $(LREF StopWatch)
     14     $(LREF benchmark)
     15 ))
     16 $(TR $(TD Flags) $(TD
     17     $(LREF AutoStart)
     18 ))
     19 ))
     20 
     21     $(RED Unlike the other modules in std.datetime, this module is not currently
     22           publicly imported in std.datetime.package, because the old
     23           versions of this functionality which use
     24           $(REF TickDuration,core,time) are in std.datetime.package and would
     25           conflict with the symbols in this module. After the old symbols have
     26           gone through the deprecation cycle and have been fully removed, then
     27           this module will be publicly imported in std.datetime.package. The
     28           old, deprecated symbols has been removed from the documentation in
     29           December 2019 and currently scheduled to be fully removed from Phobos
     30           after 2.094.)
     31 
     32     So, for now, when using std.datetime.stopwatch, if other modules from
     33     std.datetime are needed, then either import them individually rather than
     34     importing std.datetime, or use selective or static imports to import
     35     std.datetime.stopwatch. e.g.
     36 
     37     ----------------------------------------------------------------------------
     38     import std.datetime;
     39     import std.datetime.stopwatch : benchmark, StopWatch;
     40     ----------------------------------------------------------------------------
     41 
     42     The compiler will then know to use the symbols from std.datetime.stopwatch
     43     rather than the deprecated ones from std.datetime.package.
     44 
     45     License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
     46     Authors:   $(HTTP jmdavisprog.com, Jonathan M Davis) and Kato Shoichi
     47     Source:    $(PHOBOSSRC std/datetime/stopwatch.d)
     48 +/
     49 module std.datetime.stopwatch;
     50 
     51 public import core.time;
     52 import std.typecons : Flag;
     53 
     54 /++
     55     Used by StopWatch to indicate whether it should start immediately upon
     56     construction.
     57 
     58     If set to `AutoStart.no`, then the StopWatch is not started when it is
     59     constructed.
     60 
     61     Otherwise, if set to `AutoStart.yes`, then the StopWatch is started when
     62     it is constructed.
     63   +/
     64 alias AutoStart = Flag!"autoStart";
     65 
     66 
     67 /++
     68     StopWatch is used to measure time just like one would do with a physical
     69     stopwatch, including stopping, restarting, and/or resetting it.
     70 
     71     $(REF MonoTime,core,time) is used to hold the time, and it uses the system's
     72     monotonic clock, which is high precision and never counts backwards (unlike
     73     the wall clock time, which $(I can) count backwards, which is why
     74     $(REF SysTime,std,datetime,systime) should not be used for timing).
     75 
     76     Note that the precision of StopWatch differs from system to system. It is
     77     impossible for it to be the same for all systems, since the precision of the
     78     system clock and other system-dependent and situation-dependent factors
     79     (such as the overhead of a context switch between threads) varies from
     80     system to system and can affect StopWatch's accuracy.
     81   +/
     82 struct StopWatch
     83 {
     84 public:
     85 
     86     /++
     87         Constructs a StopWatch. Whether it starts immediately depends on the
     88         $(LREF AutoStart) argument.
     89 
     90         If `StopWatch.init` is used, then the constructed StopWatch isn't
     91         running (and can't be, since no constructor ran).
     92       +/
     93     this(AutoStart autostart) @safe nothrow @nogc
     94     {
     95         if (autostart)
     96             start();
     97     }
     98 
     99     ///
    100     @system nothrow @nogc unittest
    101     {
    102         import core.thread : Thread;
    103 
    104         {
    105             auto sw = StopWatch(AutoStart.yes);
    106             assert(sw.running);
    107             Thread.sleep(usecs(1));
    108             assert(sw.peek() > Duration.zero);
    109         }
    110         {
    111             auto sw = StopWatch(AutoStart.no);
    112             assert(!sw.running);
    113             Thread.sleep(usecs(1));
    114             assert(sw.peek() == Duration.zero);
    115         }
    116         {
    117             StopWatch sw;
    118             assert(!sw.running);
    119             Thread.sleep(usecs(1));
    120             assert(sw.peek() == Duration.zero);
    121         }
    122 
    123         assert(StopWatch.init == StopWatch(AutoStart.no));
    124         assert(StopWatch.init != StopWatch(AutoStart.yes));
    125     }
    126 
    127 
    128     /++
    129        Resets the StopWatch.
    130 
    131        The StopWatch can be reset while it's running, and resetting it while
    132        it's running will not cause it to stop.
    133       +/
    134     void reset() @safe nothrow @nogc
    135     {
    136         if (_running)
    137             _timeStarted = MonoTime.currTime;
    138         _ticksElapsed = 0;
    139     }
    140 
    141     ///
    142     @system nothrow @nogc unittest
    143     {
    144         import core.thread : Thread;
    145 
    146         auto sw = StopWatch(AutoStart.yes);
    147         Thread.sleep(usecs(1));
    148         sw.stop();
    149         assert(sw.peek() > Duration.zero);
    150         sw.reset();
    151         assert(sw.peek() == Duration.zero);
    152     }
    153 
    154     @system nothrow @nogc unittest
    155     {
    156         import core.thread : Thread;
    157 
    158         auto sw = StopWatch(AutoStart.yes);
    159         Thread.sleep(msecs(1));
    160         assert(sw.peek() > msecs(1));
    161         immutable before = MonoTime.currTime;
    162 
    163         // Just in case the system clock is slow enough or the system is fast
    164         // enough for the call to MonoTime.currTime inside of reset to get
    165         // the same that we just got by calling MonoTime.currTime.
    166         Thread.sleep(usecs(1));
    167 
    168         sw.reset();
    169         assert(sw.peek() < msecs(1));
    170         assert(sw._timeStarted > before);
    171         assert(sw._timeStarted <= MonoTime.currTime);
    172     }
    173 
    174 
    175     /++
    176        Starts the StopWatch.
    177 
    178        start should not be called if the StopWatch is already running.
    179       +/
    180     void start() @safe nothrow @nogc
    181     in { assert(!_running, "start was called when the StopWatch was already running."); }
    182     do
    183     {
    184         _running = true;
    185         _timeStarted = MonoTime.currTime;
    186     }
    187 
    188     ///
    189     @system nothrow @nogc unittest
    190     {
    191         import core.thread : Thread;
    192 
    193         StopWatch sw;
    194         assert(!sw.running);
    195         assert(sw.peek() == Duration.zero);
    196         sw.start();
    197         assert(sw.running);
    198         Thread.sleep(usecs(1));
    199         assert(sw.peek() > Duration.zero);
    200     }
    201 
    202 
    203     /++
    204        Stops the StopWatch.
    205 
    206        stop should not be called if the StopWatch is not running.
    207       +/
    208     void stop() @safe nothrow @nogc
    209     in { assert(_running, "stop was called when the StopWatch was not running."); }
    210     do
    211     {
    212         _running = false;
    213         _ticksElapsed += MonoTime.currTime.ticks - _timeStarted.ticks;
    214     }
    215 
    216     ///
    217     @system nothrow @nogc unittest
    218     {
    219         import core.thread : Thread;
    220 
    221         auto sw = StopWatch(AutoStart.yes);
    222         assert(sw.running);
    223         Thread.sleep(usecs(1));
    224         immutable t1 = sw.peek();
    225         assert(t1 > Duration.zero);
    226 
    227         sw.stop();
    228         assert(!sw.running);
    229         immutable t2 = sw.peek();
    230         assert(t2 >= t1);
    231         immutable t3 = sw.peek();
    232         assert(t2 == t3);
    233     }
    234 
    235 
    236     /++
    237        Peek at the amount of time that the the StopWatch has been running.
    238 
    239        This does not include any time during which the StopWatch was stopped but
    240        does include $(I all) of the time that it was running and not just the
    241        time since it was started last.
    242 
    243        Calling $(LREF reset) will reset this to `Duration.zero`.
    244       +/
    245     Duration peek() @safe const nothrow @nogc
    246     {
    247         enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1);
    248         immutable hnsecsMeasured = convClockFreq(_ticksElapsed, MonoTime.ticksPerSecond, hnsecsPerSecond);
    249         return _running ? MonoTime.currTime - _timeStarted + hnsecs(hnsecsMeasured)
    250                         : hnsecs(hnsecsMeasured);
    251     }
    252 
    253     ///
    254     @system nothrow @nogc unittest
    255     {
    256         import core.thread : Thread;
    257 
    258         auto sw = StopWatch(AutoStart.no);
    259         assert(sw.peek() == Duration.zero);
    260         sw.start();
    261 
    262         Thread.sleep(usecs(1));
    263         assert(sw.peek() >= usecs(1));
    264 
    265         Thread.sleep(usecs(1));
    266         assert(sw.peek() >= usecs(2));
    267 
    268         sw.stop();
    269         immutable stopped = sw.peek();
    270         Thread.sleep(usecs(1));
    271         assert(sw.peek() == stopped);
    272 
    273         sw.start();
    274         Thread.sleep(usecs(1));
    275         assert(sw.peek() > stopped);
    276     }
    277 
    278     @safe nothrow @nogc unittest
    279     {
    280         assert(StopWatch.init.peek() == Duration.zero);
    281     }
    282 
    283 
    284     /++
    285        Sets the total time which the StopWatch has been running (i.e. what peek
    286        returns).
    287 
    288        The StopWatch does not have to be stopped for setTimeElapsed to be
    289        called, nor will calling it cause the StopWatch to stop.
    290       +/
    291     void setTimeElapsed(Duration timeElapsed) @safe nothrow @nogc
    292     {
    293         enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1);
    294         _ticksElapsed = convClockFreq(timeElapsed.total!"hnsecs", hnsecsPerSecond, MonoTime.ticksPerSecond);
    295         _timeStarted = MonoTime.currTime;
    296     }
    297 
    298     ///
    299     @system nothrow @nogc unittest
    300     {
    301         import core.thread : Thread;
    302 
    303         StopWatch sw;
    304         sw.setTimeElapsed(hours(1));
    305 
    306         // As discussed in MonoTime's documentation, converting between
    307         // Duration and ticks is not exact, though it will be close.
    308         // How exact it is depends on the frequency/resolution of the
    309         // system's monotonic clock.
    310         assert(abs(sw.peek() - hours(1)) < usecs(1));
    311 
    312         sw.start();
    313         Thread.sleep(usecs(1));
    314         assert(sw.peek() > hours(1) + usecs(1));
    315     }
    316 
    317 
    318     /++
    319        Returns whether this StopWatch is currently running.
    320       +/
    321     @property bool running() @safe const pure nothrow @nogc
    322     {
    323         return _running;
    324     }
    325 
    326     ///
    327     @safe nothrow @nogc unittest
    328     {
    329         StopWatch sw;
    330         assert(!sw.running);
    331         sw.start();
    332         assert(sw.running);
    333         sw.stop();
    334         assert(!sw.running);
    335     }
    336 
    337 
    338 private:
    339 
    340     // We track the ticks for the elapsed time rather than a Duration so that we
    341     // don't lose any precision.
    342 
    343     bool _running = false; // Whether the StopWatch is currently running
    344     MonoTime _timeStarted; // The time the StopWatch started measuring (i.e. when it was started or reset).
    345     long _ticksElapsed;    // Total time that the StopWatch ran before it was stopped last.
    346 }
    347 
    348 /// Measure a time in milliseconds, microseconds, or nanoseconds
    349 @safe nothrow @nogc unittest
    350 {
    351     auto sw = StopWatch(AutoStart.no);
    352     sw.start();
    353     // ... Insert operations to be timed here ...
    354     sw.stop();
    355 
    356     long msecs = sw.peek.total!"msecs";
    357     long usecs = sw.peek.total!"usecs";
    358     long nsecs = sw.peek.total!"nsecs";
    359 
    360     assert(usecs >= msecs * 1000);
    361     assert(nsecs >= usecs * 1000);
    362 }
    363 
    364 ///
    365 @system nothrow @nogc unittest
    366 {
    367     import core.thread : Thread;
    368 
    369     auto sw = StopWatch(AutoStart.yes);
    370 
    371     Duration t1 = sw.peek();
    372     Thread.sleep(usecs(1));
    373     Duration t2 = sw.peek();
    374     assert(t2 > t1);
    375 
    376     Thread.sleep(usecs(1));
    377     sw.stop();
    378 
    379     Duration t3 = sw.peek();
    380     assert(t3 > t2);
    381     Duration t4 = sw.peek();
    382     assert(t3 == t4);
    383 
    384     sw.start();
    385     Thread.sleep(usecs(1));
    386 
    387     Duration t5 = sw.peek();
    388     assert(t5 > t4);
    389 
    390     // If stopping or resetting the StopWatch is not required, then
    391     // MonoTime can easily be used by itself without StopWatch.
    392     auto before = MonoTime.currTime;
    393     // do stuff...
    394     auto timeElapsed = MonoTime.currTime - before;
    395 }
    396 
    397 
    398 /++
    399     Benchmarks code for speed assessment and comparison.
    400 
    401     Params:
    402         fun = aliases of callable objects (e.g. function names). Each callable
    403               object should take no arguments.
    404         n   = The number of times each function is to be executed.
    405 
    406     Returns:
    407         The amount of time (as a $(REF Duration,core,time)) that it took to call
    408         each function `n` times. The first value is the length of time that
    409         it took to call `fun[0]` `n` times. The second value is the length
    410         of time it took to call `fun[1]` `n` times. Etc.
    411   +/
    412 Duration[fun.length] benchmark(fun...)(uint n)
    413 {
    414     Duration[fun.length] result;
    415     auto sw = StopWatch(AutoStart.yes);
    416 
    417     foreach (i, unused; fun)
    418     {
    419         sw.reset();
    420         foreach (_; 0 .. n)
    421             fun[i]();
    422         result[i] = sw.peek();
    423     }
    424 
    425     return result;
    426 }
    427 
    428 ///
    429 @safe unittest
    430 {
    431     import std.conv : to;
    432 
    433     int a;
    434     void f0() {}
    435     void f1() { auto b = a; }
    436     void f2() { auto b = to!string(a); }
    437     auto r = benchmark!(f0, f1, f2)(10_000);
    438     Duration f0Result = r[0]; // time f0 took to run 10,000 times
    439     Duration f1Result = r[1]; // time f1 took to run 10,000 times
    440     Duration f2Result = r[2]; // time f2 took to run 10,000 times
    441 }
    442 
    443 @safe nothrow unittest
    444 {
    445     import std.conv : to;
    446 
    447     int a;
    448     void f0() nothrow {}
    449     void f1() nothrow @trusted {
    450         // do not allow any optimizer to optimize this function away
    451         import core.thread : getpid;
    452         import core.stdc.stdio : printf;
    453         auto b = getpid.to!string;
    454         if (getpid == 1) // never happens, but prevents optimization
    455             printf("%p", &b);
    456     }
    457 
    458     auto sw = StopWatch(AutoStart.yes);
    459     auto r = benchmark!(f0, f1)(1000);
    460     auto total = sw.peek();
    461     assert(r[0] >= Duration.zero);
    462     assert(r[1] >= Duration.zero);
    463     assert(r[0] <= total);
    464     assert(r[1] <= total);
    465 }
    466 
    467 @safe nothrow @nogc unittest
    468 {
    469     int f0Count;
    470     int f1Count;
    471     int f2Count;
    472     void f0() nothrow @nogc { ++f0Count; }
    473     void f1() nothrow @nogc { ++f1Count; }
    474     void f2() nothrow @nogc { ++f2Count; }
    475     auto r = benchmark!(f0, f1, f2)(552);
    476     assert(f0Count == 552);
    477     assert(f1Count == 552);
    478     assert(f2Count == 552);
    479 }
    480