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 +/
thisStopWatch93 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 +/
resetStopWatch134 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 +/
startStopWatch180 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 +/
stopStopWatch208 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 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 +/
peekStopWatch245 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 +/
setTimeElapsedStopWatch291 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 +/
runningStopWatch321 @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 +/
benchmark(fun...)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;
f0()434 void f0() {}
f1()435 void f1() { auto b = a; }
f2()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;
f0()448 void f0() nothrow {}
f1()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;
f0()472 void f0() nothrow @nogc { ++f0Count; }
f1()473 void f1() nothrow @nogc { ++f1Count; }
f2()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 }
480