The OpenD Programming Language

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