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 } 426