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._timeStarted > before);
170         assert(sw._timeStarted <= MonoTime.currTime);
171     }
172 
173 
174     /++
175        Starts the StopWatch.
176 
177        start should not be called if the StopWatch is already running.
178       +/
179     void start() @safe nothrow @nogc
180     in { assert(!_running, "start was called when the StopWatch was already running."); }
181     do
182     {
183         _running = true;
184         _timeStarted = MonoTime.currTime;
185     }
186 
187     ///
188     @system nothrow @nogc unittest
189     {
190         import core.thread : Thread;
191 
192         StopWatch sw;
193         assert(!sw.running);
194         assert(sw.peek() == Duration.zero);
195         sw.start();
196         assert(sw.running);
197         Thread.sleep(usecs(1));
198         assert(sw.peek() > Duration.zero);
199     }
200 
201 
202     /++
203        Stops the StopWatch.
204 
205        stop should not be called if the StopWatch is not running.
206       +/
207     void stop() @safe nothrow @nogc
208     in { assert(_running, "stop was called when the StopWatch was not running."); }
209     do
210     {
211         _running = false;
212         _ticksElapsed += MonoTime.currTime.ticks - _timeStarted.ticks;
213     }
214 
215     ///
216     @system nothrow @nogc unittest
217     {
218         import core.thread : Thread;
219 
220         auto sw = StopWatch(AutoStart.yes);
221         assert(sw.running);
222         Thread.sleep(usecs(1));
223         immutable t1 = sw.peek();
224         assert(t1 > Duration.zero);
225 
226         sw.stop();
227         assert(!sw.running);
228         immutable t2 = sw.peek();
229         assert(t2 >= t1);
230         immutable t3 = sw.peek();
231         assert(t2 == t3);
232     }
233 
234 
235     /++
236        Peek at the amount of time that the StopWatch has been running.
237 
238        This does not include any time during which the StopWatch was stopped but
239        does include $(I all) of the time that it was running and not just the
240        time since it was started last.
241 
242        Calling $(LREF reset) will reset this to `Duration.zero`.
243       +/
244     Duration peek() @safe const nothrow @nogc
245     {
246         enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1);
247         immutable hnsecsMeasured = convClockFreq(_ticksElapsed, MonoTime.ticksPerSecond, hnsecsPerSecond);
248         return _running ? MonoTime.currTime - _timeStarted + hnsecs(hnsecsMeasured)
249                         : hnsecs(hnsecsMeasured);
250     }
251 
252     ///
253     @system nothrow @nogc unittest
254     {
255         import core.thread : Thread;
256 
257         auto sw = StopWatch(AutoStart.no);
258         assert(sw.peek() == Duration.zero);
259         sw.start();
260 
261         Thread.sleep(usecs(1));
262         assert(sw.peek() >= usecs(1));
263 
264         Thread.sleep(usecs(1));
265         assert(sw.peek() >= usecs(2));
266 
267         sw.stop();
268         immutable stopped = sw.peek();
269         Thread.sleep(usecs(1));
270         assert(sw.peek() == stopped);
271 
272         sw.start();
273         Thread.sleep(usecs(1));
274         assert(sw.peek() > stopped);
275     }
276 
277     @safe nothrow @nogc unittest
278     {
279         assert(StopWatch.init.peek() == Duration.zero);
280     }
281 
282 
283     /++
284        Sets the total time which the StopWatch has been running (i.e. what peek
285        returns).
286 
287        The StopWatch does not have to be stopped for setTimeElapsed to be
288        called, nor will calling it cause the StopWatch to stop.
289       +/
290     void setTimeElapsed(Duration timeElapsed) @safe nothrow @nogc
291     {
292         enum hnsecsPerSecond = convert!("seconds", "hnsecs")(1);
293         _ticksElapsed = convClockFreq(timeElapsed.total!"hnsecs", hnsecsPerSecond, MonoTime.ticksPerSecond);
294         _timeStarted = MonoTime.currTime;
295     }
296 
297     ///
298     @system nothrow @nogc unittest
299     {
300         import core.thread : Thread;
301 
302         StopWatch sw;
303         sw.setTimeElapsed(hours(1));
304 
305         // As discussed in MonoTime's documentation, converting between
306         // Duration and ticks is not exact, though it will be close.
307         // How exact it is depends on the frequency/resolution of the
308         // system's monotonic clock.
309         assert(abs(sw.peek() - hours(1)) < usecs(1));
310 
311         sw.start();
312         Thread.sleep(usecs(1));
313         assert(sw.peek() > hours(1) + usecs(1));
314     }
315 
316 
317     /++
318        Returns whether this StopWatch is currently running.
319       +/
320     @property bool running() @safe const pure nothrow @nogc
321     {
322         return _running;
323     }
324 
325     ///
326     @safe nothrow @nogc unittest
327     {
328         StopWatch sw;
329         assert(!sw.running);
330         sw.start();
331         assert(sw.running);
332         sw.stop();
333         assert(!sw.running);
334     }
335 
336 
337 private:
338 
339     // We track the ticks for the elapsed time rather than a Duration so that we
340     // don't lose any precision.
341 
342     bool _running = false; // Whether the StopWatch is currently running
343     MonoTime _timeStarted; // The time the StopWatch started measuring (i.e. when it was started or reset).
344     long _ticksElapsed;    // Total time that the StopWatch ran before it was stopped last.
345 }
346 
347 /// Measure a time in milliseconds, microseconds, or nanoseconds
348 @safe nothrow @nogc unittest
349 {
350     auto sw = StopWatch(AutoStart.no);
351     sw.start();
352     // ... Insert operations to be timed here ...
353     sw.stop();
354 
355     long msecs = sw.peek.total!"msecs";
356     long usecs = sw.peek.total!"usecs";
357     long nsecs = sw.peek.total!"nsecs";
358 
359     assert(usecs >= msecs * 1000);
360     assert(nsecs >= usecs * 1000);
361 }
362 
363 ///
364 @system nothrow @nogc unittest
365 {
366     import core.thread : Thread;
367 
368     auto sw = StopWatch(AutoStart.yes);
369 
370     Duration t1 = sw.peek();
371     Thread.sleep(usecs(1));
372     Duration t2 = sw.peek();
373     assert(t2 > t1);
374 
375     Thread.sleep(usecs(1));
376     sw.stop();
377 
378     Duration t3 = sw.peek();
379     assert(t3 > t2);
380     Duration t4 = sw.peek();
381     assert(t3 == t4);
382 
383     sw.start();
384     Thread.sleep(usecs(1));
385 
386     Duration t5 = sw.peek();
387     assert(t5 > t4);
388 
389     // If stopping or resetting the StopWatch is not required, then
390     // MonoTime can easily be used by itself without StopWatch.
391     auto before = MonoTime.currTime;
392     // do stuff...
393     auto timeElapsed = MonoTime.currTime - before;
394 }
395 
396 
397 /++
398     Benchmarks code for speed assessment and comparison.
399 
400     Params:
401         fun = aliases of callable objects (e.g. function names). Each callable
402               object should take no arguments.
403         n   = The number of times each function is to be executed.
404 
405     Returns:
406         The amount of time (as a $(REF Duration,core,time)) that it took to call
407         each function `n` times. The first value is the length of time that
408         it took to call `fun[0]` `n` times. The second value is the length
409         of time it took to call `fun[1]` `n` times. Etc.
410   +/
411 Duration[fun.length] benchmark(fun...)(uint n)
412 {
413     Duration[fun.length] result;
414     auto sw = StopWatch(AutoStart.yes);
415 
416     foreach (i, unused; fun)
417     {
418         sw.reset();
419         foreach (_; 0 .. n)
420             fun[i]();
421         result[i] = sw.peek();
422     }
423 
424     return result;
425 }
426 
427 ///
428 @safe unittest
429 {
430     import std.conv : to;
431 
432     int a;
433     void f0() {}
434     void f1() { auto b = a; }
435     void f2() { auto b = to!string(a); }
436     auto r = benchmark!(f0, f1, f2)(10_000);
437     Duration f0Result = r[0]; // time f0 took to run 10,000 times
438     Duration f1Result = r[1]; // time f1 took to run 10,000 times
439     Duration f2Result = r[2]; // time f2 took to run 10,000 times
440 }
441 
442 @safe nothrow unittest
443 {
444     import std.conv : to;
445 
446     int a;
447     void f0() nothrow {}
448     void f1() nothrow @trusted {
449         // do not allow any optimizer to optimize this function away
450         import core.thread : getpid;
451         import core.stdc.stdio : printf;
452         auto b = getpid.to!string;
453         if (getpid == 1) // never happens, but prevents optimization
454             printf("%p", &b);
455     }
456 
457     auto sw = StopWatch(AutoStart.yes);
458     auto r = benchmark!(f0, f1)(1000);
459     auto total = sw.peek();
460     assert(r[0] >= Duration.zero);
461     assert(r[1] >= Duration.zero);
462     assert(r[0] <= total);
463     assert(r[1] <= total);
464 }
465 
466 @safe nothrow @nogc unittest
467 {
468     int f0Count;
469     int f1Count;
470     int f2Count;
471     void f0() nothrow @nogc { ++f0Count; }
472     void f1() nothrow @nogc { ++f1Count; }
473     void f2() nothrow @nogc { ++f2Count; }
474     auto r = benchmark!(f0, f1, f2)(552);
475     assert(f0Count == 552);
476     assert(f1Count == 552);
477     assert(f2Count == 552);
478 }