1 /// It's about time 2 module mecca.lib.time; 3 4 // Licensed under the Boost license. Full copyright information in the AUTHORS file 5 6 import core.sys.posix.sys.time : timespec; 7 8 public import std.datetime; 9 import mecca.lib.division: S64Divisor; 10 public import mecca.platform.x86: readTSC; 11 import mecca.log; 12 13 /** 14 * A time point maintained through the TSC timer 15 * 16 * TSC is a time counter maintained directly by the CPU. It counts how many "cycles" (loosly corresponding to actual CPU cycles) 17 * since an arbitrary start point. It is read by a single assembly instruciton, and is more efficient to read than kernel 18 * operatons. 19 */ 20 struct TscTimePoint { 21 private enum HECTONANO = 10_000_000; 22 /// Minimal, maximal and zero constants for reference. 23 enum min = TscTimePoint(long.min); 24 enum zero = TscTimePoint(0); /// ditto 25 enum max = TscTimePoint(long.max); /// ditto 26 27 /// Provide the cycles/time ratio for the current machine. 28 static shared immutable long cyclesPerSecond; 29 static shared immutable long cyclesPerMsec; /// ditto 30 static shared immutable long cyclesPerUsec; /// ditto 31 alias frequency = cyclesPerSecond; /// ditto 32 33 /** Prepared dividor for the cycles/time value 34 * 35 * An S64Divisor for the cycles/time value, for quickly dividing by it at runtime. 36 */ 37 static shared immutable S64Divisor cyclesPerSecondDivisor; 38 static shared immutable S64Divisor cyclesPerMsecDivisor; /// ditto 39 static shared immutable S64Divisor cyclesPerUsecDivisor; /// ditto 40 41 private: 42 /* thread local */ static ubyte refetchInterval; // How many soft calls to do before doing hard fetch of timer 43 /* thread local */ static ubyte fetchCounter; 44 /* thread local */ static TscTimePoint lastTsc; 45 46 public: 47 /// Time represented by TscTimePoint in units of cycles. 48 long cycles; 49 50 /** 51 * Get a TscTimePoint representing now. 52 * 53 * There are two variants of this method. "now" and "hardNow". By default, they do exactly the same thing: 54 * report the time right now, as reported by the cycles counter in the CPU. 55 * 56 * As fetching the cycles counter may be relatively expensive, threads that do a lot of time keeping may find that getting 57 * the hardware counter each and every time is too costly. In that case, you can call "setHardNowThreshold" with a threshold. 58 * Calling, e.g. setHardNow(3) will mean that now will call hardNow every third invocation. 59 * 60 * Even when now doesn't call hardNow, it is still guaranteed to montoniously advance, but it not guaranteed to be accurate. 61 * 62 * A good rule of thumb is this: If you use requires getting the time on a semi-regular basis, call now whenever precise 63 * time is not required. If your use requires time only sporadically, only use hardNow. 64 * 65 * Warning: 66 * now does not guarantee monotonity across different threads. If you need different threads to have comparable times, use 67 * hardNow. 68 */ 69 static TscTimePoint now() nothrow @nogc @safe { 70 if (fetchCounter < refetchInterval || refetchInterval==ubyte.max) { 71 fetchCounter++; 72 lastTsc.cycles++; 73 return lastTsc; 74 } 75 else { 76 return hardNow(); 77 } 78 } 79 80 /// ditto 81 static TscTimePoint hardNow() nothrow @nogc @safe { 82 pragma(inline, true); 83 lastTsc.cycles = readTSC(); 84 fetchCounter = 0; 85 return lastTsc; 86 } 87 88 /** 89 * Set the frequency with which we take a hard TSC reading 90 * 91 * See complete documentation in the now method. 92 * 93 * Params: 94 * interval = take a hardNow every so many calls to now. A value of 1 mean that now and hardNow are identical. A value of 0 95 * means that hardNow is $(B never) implicitly taken. 96 */ 97 static void setHardNowThreshold(ubyte interval) nothrow @nogc @safe { 98 refetchInterval = cast(ubyte)(interval-1); 99 } 100 101 shared static this() { 102 import mecca.platform.os: calculateCycles; 103 104 const cycles = calculateCycles(); 105 cyclesPerSecond = cycles.perSecond; 106 cyclesPerMsec = cycles.perMsec; 107 cyclesPerUsec = cycles.perUsec; 108 109 cyclesPerSecondDivisor = S64Divisor(cyclesPerSecond); 110 cyclesPerMsecDivisor = S64Divisor(cyclesPerMsec); 111 cyclesPerUsecDivisor = S64Divisor(cyclesPerUsec); 112 113 hardNow(); 114 } 115 116 /// Calculate a TscTimePoint for a set duration from now 117 static auto fromNow(Duration dur) @nogc { 118 return hardNow + toCycles(dur); 119 } 120 121 /// Calculate a TscTimePoint for a time given in systime from now 122 /// 123 /// Not @nogc and nothrow, because Clock.currTime does GC and may throw 124 @("notrace") static auto fromSysTime(SysTime time) @safe { 125 Duration diff = time - Clock.currTime(); 126 return fromNow(diff); 127 } 128 129 /// Various conversion functions 130 static long toCycles(Duration dur) @nogc @safe nothrow { 131 long hns = dur.total!"hnsecs"; 132 return (hns / HECTONANO) * cyclesPerSecond + ((hns % HECTONANO) * cyclesPerSecond) / HECTONANO; 133 } 134 /// ditto 135 static long toCycles(string unit)(long n) @nogc @safe nothrow { 136 static if (unit == "usecs") { 137 return n * cyclesPerUsec; 138 } else static if (unit == "msecs") { 139 return n * cyclesPerMsec; 140 } else static if (unit == "seconds") { 141 return n * cyclesPerSecond; 142 } 143 } 144 /// ditto 145 static Duration toDuration(long cycles) pure @safe @nogc nothrow { 146 return hnsecs((cycles / cyclesPerSecond) * HECTONANO + ((cycles % cyclesPerSecond) * HECTONANO) / cyclesPerSecond); 147 } 148 /// ditto 149 Duration toDuration() const @safe @nogc nothrow { 150 return hnsecs((cycles / cyclesPerSecond) * HECTONANO + ((cycles % cyclesPerSecond) * HECTONANO) / cyclesPerSecond); 151 } 152 /// ditto 153 static long toUsecs(long cycles) pure @nogc @safe nothrow { 154 return cycles / cyclesPerUsecDivisor; 155 } 156 /// ditto 157 long toUsecs() const pure @nogc @safe nothrow { 158 return cycles / cyclesPerUsecDivisor; 159 } 160 /// ditto 161 static long toMsecs(long cycles) pure @nogc @safe nothrow { 162 return cycles / cyclesPerMsecDivisor; 163 } 164 /// ditto 165 long toMsecs() pure const @nogc @safe nothrow { 166 return cycles / cyclesPerMsecDivisor; 167 } 168 169 int opCmp(TscTimePoint rhs) pure const @nogc @safe nothrow { 170 return (cycles > rhs.cycles) ? 1 : ((cycles < rhs.cycles) ? -1 : 0); 171 } 172 bool opEquals()(TscTimePoint rhs) pure const @nogc @safe nothrow { 173 return cycles == rhs.cycles; 174 } 175 176 TscTimePoint opBinary(string op: "+")(long cycles) const @nogc @safe nothrow pure { 177 return TscTimePoint(this.cycles + cycles); 178 } 179 TscTimePoint opBinary(string op: "+")(Duration dur) const @nogc @safe nothrow { 180 return TscTimePoint(cycles + toCycles(dur)); 181 } 182 183 TscTimePoint opBinary(string op: "-")(long cycles) const @nogc @safe nothrow pure { 184 return TscTimePoint(this.cycles - cycles); 185 } 186 Duration opBinary(string op: "-")(TscTimePoint rhs) const @nogc @safe nothrow pure { 187 return TscTimePoint.toDuration(cycles - rhs.cycles); 188 } 189 TscTimePoint opBinary(string op: "-")(Duration dur) const @nogc @safe nothrow { 190 return TscTimePoint(cycles - toCycles(dur)); 191 } 192 193 ref auto opOpAssign(string op)(Duration dur) @nogc if (op == "+" || op == "-") { 194 mixin("cycles " ~ op ~ "= toCycles(dur);"); 195 return this; 196 } 197 ref auto opOpAssign(string op)(long cycles) @nogc if (op == "+" || op == "-") { 198 mixin("this.cycles " ~ op ~ "= cycles;"); 199 return this; 200 } 201 202 /// Calculate difference between two TscTimePoint in the given units 203 @("notrace") long diff(string units)(TscTimePoint rhs) nothrow @safe @nogc 204 if (units == "usecs" || units == "msecs" || units == "seconds" || units == "cycles") 205 { 206 static if (units == "usecs") { 207 return (cycles - rhs.cycles) / cyclesPerUsecDivisor; 208 } 209 else static if (units == "msecs") { 210 return (cycles - rhs.cycles) / cyclesPerMsecDivisor; 211 } 212 else static if (units == "seconds") { 213 return (cycles - rhs.cycles) / cyclesPerSecondDivisor; 214 } 215 else static if (units == "cycles") { 216 return (cycles - rhs.cycles); 217 } 218 else { 219 static assert (false, units); 220 } 221 } 222 223 /// Convert to any of the units accepted by toDuration 224 long to(string unit)() const @nogc @safe nothrow { 225 return toDuration.total!unit(); 226 } 227 } 228 229 unittest { 230 auto t0 = TscTimePoint.hardNow; 231 assert (t0.cycles > 0); 232 assert (TscTimePoint.cyclesPerSecond > 1_000_000); 233 } 234 235 /// A type for specifying absolute timeouts 236 struct Timeout { 237 /// Constant specifying an already elapsed timeout 238 enum Timeout elapsed = Timeout(TscTimePoint.min); 239 /// Constant specifying a timeout that will never elapse 240 enum Timeout infinite = Timeout(TscTimePoint.max); 241 242 /// The expected expiry time 243 TscTimePoint expiry; 244 245 /// Construct a timeout from TscTimePoint 246 @safe @nogc pure nothrow 247 this(TscTimePoint expiry) { 248 this.expiry = expiry; 249 } 250 251 /** 252 * Construct a timeout from Duration 253 * 254 * Params: 255 * dur = Duration until timeout expires 256 * now = If provided, the base time to compute the timeout relative to 257 */ 258 this(Duration dur, TscTimePoint now = TscTimePoint.now) nothrow @safe @nogc { 259 if (dur == Duration.max) { 260 this.expiry = TscTimePoint.max; 261 } 262 else { 263 this.expiry = now + dur; 264 } 265 } 266 267 /** 268 * Report how much time until the timeout expires 269 */ 270 @notrace @property Duration remaining(TscTimePoint now = TscTimePoint.now) const @safe @nogc nothrow { 271 if (expiry == TscTimePoint.max) { 272 return Duration.max; 273 } 274 if (expiry.cycles < now.cycles) { 275 return Duration.zero; 276 } 277 return expiry - now; 278 } 279 280 /** 281 * Checks whether a Timeout has expired. 282 * 283 * Params: 284 * now = time point relative to which to check. 285 */ 286 @notrace @property bool expired(TscTimePoint now = TscTimePoint.now) pure const nothrow @safe @nogc { 287 if( this == infinite ) 288 return false; 289 290 return expiry <= now; 291 } 292 293 @notrace int opCmp(in Timeout rhs) const nothrow @safe @nogc { 294 return expiry.opCmp(rhs.expiry); 295 } 296 297 @notrace bool opEquals()(in Timeout rhs) const nothrow @safe @nogc { 298 return expiry == rhs.expiry; 299 } 300 } 301 302 package(mecca) timespec toTimespec(Duration duration) nothrow pure @safe @nogc 303 { 304 timespec spec; 305 duration.split!("seconds", "nsecs")(spec.tv_sec, spec.tv_nsec); 306 307 return spec; 308 }