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 }