diff --git a/rd-net/Lifetimes/Util/LocalStopwatch.cs b/rd-net/Lifetimes/Util/LocalStopwatch.cs index ee3ea015a..6599ccb8d 100644 --- a/rd-net/Lifetimes/Util/LocalStopwatch.cs +++ b/rd-net/Lifetimes/Util/LocalStopwatch.cs @@ -56,4 +56,55 @@ private void AssertTimeStamp() if (Mode.IsAssertion) Assertion.Assert(myStartTimeStamp != 0, $"{nameof(LocalStopwatch)} must be created using `{nameof(StartNew)}` method"); } } -} \ No newline at end of file + + /// + /// Non-allocating, low-resolution stopwatch backed by on + /// .NET 5+ and by on older targets. Resolution is whatever + /// the OS tick granularity is (typically ~10–16 ms on Windows), which makes this much cheaper + /// than when sub-millisecond accuracy is not needed. + /// + /// + /// + /// Always create instances via ; a default-constructed value will trip an + /// assertion in . + /// + /// + /// Warning — wrap-around on pre-.NET 5 targets (net472 / netstandard2.0): + /// the underlying is a 32-bit counter. The unsigned + /// subtraction used here recovers the correct elapsed value across one wrap, giving an + /// unambiguous range of ~49.71 days (2^32 ms). If the stopwatch is read after a longer + /// interval the result silently aliases (true elapsed modulo ~49.71 days) — do not rely on + /// this type for measurements that may exceed that bound on pre-net5 targets. The .NET 5+ + /// branch uses a 64-bit counter and is not subject to this limit in practice. + /// + /// + public readonly struct FastStopwatch + { +#if NET5_0_OR_GREATER + private readonly long myStartTimeStamp; + private FastStopwatch(long startTimestamp) => myStartTimeStamp = startTimestamp; + private static long GetTicks() => Environment.TickCount64; +#else + private readonly uint myStartTimeStamp; + private FastStopwatch(uint startTimestamp) => myStartTimeStamp = startTimestamp; + private static uint GetTicks() => (uint)Environment.TickCount; +#endif + + public static FastStopwatch StartNew() => new(GetTicks()); + public long ElapsedMilliseconds + { + get + { + AssertTimeStamp(); + return unchecked(GetTicks() - myStartTimeStamp); + } + } + + public TimeSpan Elapsed => TimeSpan.FromMilliseconds(ElapsedMilliseconds); + + private void AssertTimeStamp() + { + if (Mode.IsAssertion) Assertion.Assert(myStartTimeStamp != 0, $"{nameof(FastStopwatch)} must be created using `{nameof(StartNew)}` method"); + } + } +}