145
|
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 +/
|
|
97 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 +/
|
|
138 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 +/
|
|
184 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 +/
|
|
212 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 +/
|
|
249 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 +/
|
|
295 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 +/
|
|
325 @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 +/
|
|
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;
|
|
389 void f0() {}
|
|
390 void f1() { auto b = a; }
|
|
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;
|
|
403 void f0() nothrow {}
|
|
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;
|
|
418 void f0() nothrow @nogc { ++f0Count; }
|
|
419 void f1() nothrow @nogc { ++f1Count; }
|
|
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 }
|