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 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 }