| // Written in the D programming language |
| |
| /++ |
| Module containing some basic benchmarking and timing functionality. |
| |
| For convenience, this module publicly imports $(MREF core,time). |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(DIVC quickindex, |
| $(BOOKTABLE, |
| $(TR $(TH Category) $(TH Functions)) |
| $(TR $(TD Main functionality) $(TD |
| $(LREF StopWatch) |
| $(LREF benchmark) |
| )) |
| $(TR $(TD Flags) $(TD |
| $(LREF AutoStart) |
| )) |
| )) |
| |
| $(RED Unlike the other modules in std.datetime, this module is not currently |
| publicly imported in std.datetime.package, because the old |
| versions of this functionality which use |
| $(REF TickDuration,core,time) are in std.datetime.package and would |
| conflict with the symbols in this module. After the old symbols have |
| gone through the deprecation cycle and have been fully removed, then |
| this module will be publicly imported in std.datetime.package. The |
| old, deprecated symbols has been removed from the documentation in |
| December 2019 and currently scheduled to be fully removed from Phobos |
| after 2.094.) |
| |
| So, for now, when using std.datetime.stopwatch, if other modules from |
| std.datetime are needed, then either import them individually rather than |
| importing std.datetime, or use selective or static imports to import |
| std.datetime.stopwatch. e.g. |
| |
| ---------------------------------------------------------------------------- |
| import std.datetime; |
| import std.datetime.stopwatch : benchmark, StopWatch; |
| ---------------------------------------------------------------------------- |
| |
| The compiler will then know to use the symbols from std.datetime.stopwatch |
| rather than the deprecated ones from std.datetime.package. |
| |
| License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| Authors: $(HTTP jmdavisprog.com, Jonathan M Davis) and Kato Shoichi |
| Source: $(PHOBOSSRC std/datetime/stopwatch.d) |
| +/ |
| module std.datetime.stopwatch; |
| |
| public import core.time; |
| import std.typecons : Flag; |
| |
| /++ |
| Used by StopWatch to indicate whether it should start immediately upon |
| construction. |
| |
| If set to `AutoStart.no`, then the StopWatch is not started when it is |
| constructed. |
| |
| Otherwise, if set to `AutoStart.yes`, then the StopWatch is started when |
| it is constructed. |
| +/ |
| alias AutoStart = Flag!"autoStart"; |
| |
| |
| /++ |
| StopWatch is used to measure time just like one would do with a physical |
| stopwatch, including stopping, restarting, and/or resetting it. |
| |
| $(REF MonoTime,core,time) is used to hold the time, and it uses the system's |
| monotonic clock, which is high precision and never counts backwards (unlike |
| the wall clock time, which $(I can) count backwards, which is why |
| $(REF SysTime,std,datetime,systime) should not be used for timing). |
| |
| Note that the precision of StopWatch differs from system to system. It is |
| impossible for it to be the same for all systems, since the precision of the |
| system clock and other system-dependent and situation-dependent factors |
| (such as the overhead of a context switch between threads) varies from |
| system to system and can affect StopWatch's accuracy. |
| +/ |
| struct StopWatch |
| { |
| public: |
| |
| /++ |
| Constructs a StopWatch. Whether it starts immediately depends on the |
| $(LREF AutoStart) argument. |
| |
| If `StopWatch.init` is used, then the constructed StopWatch isn't |
| running (and can't be, since no constructor ran). |
| +/ |
| this(AutoStart autostart) @safe nothrow @nogc |
| { |
| if (autostart) |
| start(); |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| { |
| auto sw = StopWatch(AutoStart.yes); |
| assert(sw.running); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() > Duration.zero); |
| } |
| { |
| auto sw = StopWatch(AutoStart.no); |
| assert(!sw.running); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() == Duration.zero); |
| } |
| { |
| StopWatch sw; |
| assert(!sw.running); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() == Duration.zero); |
| } |
| |
| assert(StopWatch.init == StopWatch(AutoStart.no)); |
| assert(StopWatch.init != StopWatch(AutoStart.yes)); |
| } |
| |
| |
| /++ |
| Resets the StopWatch. |
| |
| The StopWatch can be reset while it's running, and resetting it while |
| it's running will not cause it to stop. |
| +/ |
| void reset() @safe nothrow @nogc |
| { |
| if (_running) |
| _timeStarted = MonoTime.currTime; |
| _ticksElapsed = 0; |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| auto sw = StopWatch(AutoStart.yes); |
| Thread.sleep(usecs(1)); |
| sw.stop(); |
| assert(sw.peek() > Duration.zero); |
| sw.reset(); |
| assert(sw.peek() == Duration.zero); |
| } |
| |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| auto sw = StopWatch(AutoStart.yes); |
| Thread.sleep(msecs(1)); |
| assert(sw.peek() > msecs(1)); |
| immutable before = MonoTime.currTime; |
| |
| // Just in case the system clock is slow enough or the system is fast |
| // enough for the call to MonoTime.currTime inside of reset to get |
| // the same that we just got by calling MonoTime.currTime. |
| Thread.sleep(usecs(1)); |
| |
| sw.reset(); |
| assert(sw.peek() < msecs(1)); |
| assert(sw._timeStarted > before); |
| assert(sw._timeStarted <= MonoTime.currTime); |
| } |
| |
| |
| /++ |
| Starts the StopWatch. |
| |
| start should not be called if the StopWatch is already running. |
| +/ |
| void start() @safe nothrow @nogc |
| in { assert(!_running, "start was called when the StopWatch was already running."); } |
| do |
| { |
| _running = true; |
| _timeStarted = MonoTime.currTime; |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| StopWatch sw; |
| assert(!sw.running); |
| assert(sw.peek() == Duration.zero); |
| sw.start(); |
| assert(sw.running); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() > Duration.zero); |
| } |
| |
| |
| /++ |
| Stops the StopWatch. |
| |
| stop should not be called if the StopWatch is not running. |
| +/ |
| void stop() @safe nothrow @nogc |
| in { assert(_running, "stop was called when the StopWatch was not running."); } |
| do |
| { |
| _running = false; |
| _ticksElapsed += MonoTime.currTime.ticks - _timeStarted.ticks; |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| auto sw = StopWatch(AutoStart.yes); |
| assert(sw.running); |
| Thread.sleep(usecs(1)); |
| immutable t1 = sw.peek(); |
| assert(t1 > Duration.zero); |
| |
| sw.stop(); |
| assert(!sw.running); |
| immutable t2 = sw.peek(); |
| assert(t2 >= t1); |
| immutable t3 = sw.peek(); |
| assert(t2 == t3); |
| } |
| |
| |
| /++ |
| Peek at the amount of time that the the StopWatch has been running. |
| |
| This does not include any time during which the StopWatch was stopped but |
| does include $(I all) of the time that it was running and not just the |
| time since it was started last. |
| |
| Calling $(LREF reset) will reset this to `Duration.zero`. |
| +/ |
| Duration peek() @safe const nothrow @nogc |
| { |
| enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1); |
| immutable hnsecsMeasured = convClockFreq(_ticksElapsed, MonoTime.ticksPerSecond, hnsecsPerSecond); |
| return _running ? MonoTime.currTime - _timeStarted + hnsecs(hnsecsMeasured) |
| : hnsecs(hnsecsMeasured); |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| auto sw = StopWatch(AutoStart.no); |
| assert(sw.peek() == Duration.zero); |
| sw.start(); |
| |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() >= usecs(1)); |
| |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() >= usecs(2)); |
| |
| sw.stop(); |
| immutable stopped = sw.peek(); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() == stopped); |
| |
| sw.start(); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() > stopped); |
| } |
| |
| @safe nothrow @nogc unittest |
| { |
| assert(StopWatch.init.peek() == Duration.zero); |
| } |
| |
| |
| /++ |
| Sets the total time which the StopWatch has been running (i.e. what peek |
| returns). |
| |
| The StopWatch does not have to be stopped for setTimeElapsed to be |
| called, nor will calling it cause the StopWatch to stop. |
| +/ |
| void setTimeElapsed(Duration timeElapsed) @safe nothrow @nogc |
| { |
| enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1); |
| _ticksElapsed = convClockFreq(timeElapsed.total!"hnsecs", hnsecsPerSecond, MonoTime.ticksPerSecond); |
| _timeStarted = MonoTime.currTime; |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| StopWatch sw; |
| sw.setTimeElapsed(hours(1)); |
| |
| // As discussed in MonoTime's documentation, converting between |
| // Duration and ticks is not exact, though it will be close. |
| // How exact it is depends on the frequency/resolution of the |
| // system's monotonic clock. |
| assert(abs(sw.peek() - hours(1)) < usecs(1)); |
| |
| sw.start(); |
| Thread.sleep(usecs(1)); |
| assert(sw.peek() > hours(1) + usecs(1)); |
| } |
| |
| |
| /++ |
| Returns whether this StopWatch is currently running. |
| +/ |
| @property bool running() @safe const pure nothrow @nogc |
| { |
| return _running; |
| } |
| |
| /// |
| @safe nothrow @nogc unittest |
| { |
| StopWatch sw; |
| assert(!sw.running); |
| sw.start(); |
| assert(sw.running); |
| sw.stop(); |
| assert(!sw.running); |
| } |
| |
| |
| private: |
| |
| // We track the ticks for the elapsed time rather than a Duration so that we |
| // don't lose any precision. |
| |
| bool _running = false; // Whether the StopWatch is currently running |
| MonoTime _timeStarted; // The time the StopWatch started measuring (i.e. when it was started or reset). |
| long _ticksElapsed; // Total time that the StopWatch ran before it was stopped last. |
| } |
| |
| /// Measure a time in milliseconds, microseconds, or nanoseconds |
| @safe nothrow @nogc unittest |
| { |
| auto sw = StopWatch(AutoStart.no); |
| sw.start(); |
| // ... Insert operations to be timed here ... |
| sw.stop(); |
| |
| long msecs = sw.peek.total!"msecs"; |
| long usecs = sw.peek.total!"usecs"; |
| long nsecs = sw.peek.total!"nsecs"; |
| |
| assert(usecs >= msecs * 1000); |
| assert(nsecs >= usecs * 1000); |
| } |
| |
| /// |
| @system nothrow @nogc unittest |
| { |
| import core.thread : Thread; |
| |
| auto sw = StopWatch(AutoStart.yes); |
| |
| Duration t1 = sw.peek(); |
| Thread.sleep(usecs(1)); |
| Duration t2 = sw.peek(); |
| assert(t2 > t1); |
| |
| Thread.sleep(usecs(1)); |
| sw.stop(); |
| |
| Duration t3 = sw.peek(); |
| assert(t3 > t2); |
| Duration t4 = sw.peek(); |
| assert(t3 == t4); |
| |
| sw.start(); |
| Thread.sleep(usecs(1)); |
| |
| Duration t5 = sw.peek(); |
| assert(t5 > t4); |
| |
| // If stopping or resetting the StopWatch is not required, then |
| // MonoTime can easily be used by itself without StopWatch. |
| auto before = MonoTime.currTime; |
| // do stuff... |
| auto timeElapsed = MonoTime.currTime - before; |
| } |
| |
| |
| /++ |
| Benchmarks code for speed assessment and comparison. |
| |
| Params: |
| fun = aliases of callable objects (e.g. function names). Each callable |
| object should take no arguments. |
| n = The number of times each function is to be executed. |
| |
| Returns: |
| The amount of time (as a $(REF Duration,core,time)) that it took to call |
| each function `n` times. The first value is the length of time that |
| it took to call `fun[0]` `n` times. The second value is the length |
| of time it took to call `fun[1]` `n` times. Etc. |
| +/ |
| Duration[fun.length] benchmark(fun...)(uint n) |
| { |
| Duration[fun.length] result; |
| auto sw = StopWatch(AutoStart.yes); |
| |
| foreach (i, unused; fun) |
| { |
| sw.reset(); |
| foreach (_; 0 .. n) |
| fun[i](); |
| result[i] = sw.peek(); |
| } |
| |
| return result; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.conv : to; |
| |
| int a; |
| void f0() {} |
| void f1() { auto b = a; } |
| void f2() { auto b = to!string(a); } |
| auto r = benchmark!(f0, f1, f2)(10_000); |
| Duration f0Result = r[0]; // time f0 took to run 10,000 times |
| Duration f1Result = r[1]; // time f1 took to run 10,000 times |
| Duration f2Result = r[2]; // time f2 took to run 10,000 times |
| } |
| |
| @safe nothrow unittest |
| { |
| import std.conv : to; |
| |
| int a; |
| void f0() nothrow {} |
| void f1() nothrow @trusted { |
| // do not allow any optimizer to optimize this function away |
| import core.thread : getpid; |
| import core.stdc.stdio : printf; |
| auto b = getpid.to!string; |
| if (getpid == 1) // never happens, but prevents optimization |
| printf("%p", &b); |
| } |
| |
| auto sw = StopWatch(AutoStart.yes); |
| auto r = benchmark!(f0, f1)(1000); |
| auto total = sw.peek(); |
| assert(r[0] >= Duration.zero); |
| assert(r[1] >= Duration.zero); |
| assert(r[0] <= total); |
| assert(r[1] <= total); |
| } |
| |
| @safe nothrow @nogc unittest |
| { |
| int f0Count; |
| int f1Count; |
| int f2Count; |
| void f0() nothrow @nogc { ++f0Count; } |
| void f1() nothrow @nogc { ++f1Count; } |
| void f2() nothrow @nogc { ++f2Count; } |
| auto r = benchmark!(f0, f1, f2)(552); |
| assert(f0Count == 552); |
| assert(f1Count == 552); |
| assert(f2Count == 552); |
| } |