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