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 }