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 }