Created
March 12, 2015 03:58
-
-
Save cameronism/22cbf273dee9fc8782fc to your computer and use it in GitHub Desktop.
Low overhead nested stopwatch for .NET
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public sealed class NestedStopwatch | |
| { | |
| public struct Split : IDisposable | |
| { | |
| private NestedStopwatch _parent; | |
| private int _index; | |
| internal Split(NestedStopwatch sw, int index) | |
| { | |
| _parent = sw; | |
| _index = index; | |
| } | |
| public Split Begin(string name) | |
| { | |
| var ix = _parent.BeginChild(_index, name); | |
| return new Split(_parent, ix); | |
| } | |
| public void Restart() | |
| { | |
| _parent.Restart(_index); | |
| } | |
| public void Dispose() | |
| { | |
| _parent.Stop(_index); | |
| } | |
| public IEnumerable<Split> GetChildren() | |
| { | |
| var jobs = _parent._jobs; | |
| var next = jobs[_index].FirstChild; | |
| while (next != 0) | |
| { | |
| yield return new Split(_parent, next); | |
| next = jobs[next].NextSibling; | |
| } | |
| } | |
| public void Print(TextWriter tw, string format = "{0} {1}") | |
| { | |
| _parent.Print(tw, _parent._jobs, format, _index, 0); | |
| } | |
| public TimeSpan Elapsed | |
| { | |
| get | |
| { | |
| return _parent._jobs[_index].Elapsed; | |
| } | |
| } | |
| public string Name | |
| { | |
| get | |
| { | |
| return _parent._jobs[_index].Name; | |
| } | |
| } | |
| public bool Running | |
| { | |
| get | |
| { | |
| return _parent._jobs[_index].StopTimestamp == 0; | |
| } | |
| } | |
| public bool HasChildren | |
| { | |
| get | |
| { | |
| return _parent._jobs[_index].LastChild != 0; | |
| } | |
| } | |
| public int TotalStopwatchCount | |
| { | |
| get | |
| { | |
| return _parent._offset; | |
| } | |
| } | |
| } | |
| struct Job | |
| { | |
| public long StartTimestamp; | |
| public long StopTimestamp; | |
| public string Name; | |
| public ushort FirstChild; | |
| public ushort LastChild; | |
| public ushort NextSibling; | |
| public TimeSpan Elapsed | |
| { | |
| get | |
| { | |
| var stop = StopTimestamp; | |
| if (stop == 0) | |
| stop = GetTimestamp(); | |
| return GetElapsed(StartTimestamp, stop); | |
| } | |
| } | |
| public void SetLastChild(Job[] jobs, ushort child) | |
| { | |
| if (FirstChild == 0) | |
| { | |
| FirstChild = child; | |
| LastChild = child; | |
| } | |
| else | |
| { | |
| jobs[LastChild].NextSibling = child; | |
| LastChild = child; | |
| } | |
| } | |
| public void Restart() | |
| { | |
| StopTimestamp = 0; | |
| StartTimestamp = GetTimestamp(); | |
| } | |
| } | |
| private static readonly double tickFrequency = TimeSpan.FromSeconds(1).Ticks / Stopwatch.Frequency; | |
| private readonly object _lock = new Object(); | |
| private Job[] _jobs; | |
| private int _offset; | |
| private NestedStopwatch() | |
| { | |
| } | |
| [MethodImpl(MethodImplOptions.NoInlining)] | |
| private Job[] Resize() | |
| { | |
| var newLength = Math.Min(_jobs.Length * 2, ushort.MaxValue); | |
| Array.Resize(ref _jobs, newLength); | |
| return _jobs; | |
| } | |
| private void Stop(int index) | |
| { | |
| var ts = GetTimestamp(); | |
| lock (_lock) | |
| { | |
| _jobs[index].StopTimestamp = ts; | |
| } | |
| } | |
| private void Restart(int index) | |
| { | |
| lock (_lock) | |
| { | |
| _jobs[index].Restart(); | |
| } | |
| } | |
| private int BeginChild(int parentIndex, string name) | |
| { | |
| int newIndex; | |
| lock (_lock) | |
| { | |
| var jobs = _jobs; | |
| newIndex = _offset++; | |
| if (newIndex > ushort.MaxValue) | |
| ThrowOverflow(); | |
| if (newIndex >= jobs.Length) | |
| jobs = Resize(); | |
| // set name before pointing to it | |
| jobs[newIndex].Name = name; | |
| jobs[parentIndex].SetLastChild(jobs, (ushort)newIndex); | |
| jobs[newIndex].StartTimestamp = GetTimestamp(); | |
| } | |
| return newIndex; | |
| } | |
| private void Print(TextWriter writer, Job[] jobs, string format, int ix, int indent) | |
| { | |
| for (int i = 0; i < indent; i++) | |
| { | |
| writer.Write(' '); | |
| } | |
| writer.WriteLine(format, jobs[ix].Elapsed, jobs[ix].Name); | |
| indent += 2; | |
| var child = jobs[ix].FirstChild; | |
| while (child != 0) | |
| { | |
| Print(writer, jobs, format, child, indent); | |
| child = jobs[child].NextSibling; | |
| } | |
| } | |
| public static Split Begin(string name, int initialCount = 64) | |
| { | |
| if (initialCount < 1 || initialCount > ushort.MaxValue) throw new ArgumentOutOfRangeException("initialCount"); | |
| var sw = new NestedStopwatch(); | |
| sw._offset = 1; | |
| var jobs = sw._jobs = new Job[initialCount]; | |
| jobs[0].Name = name; | |
| jobs[0].StartTimestamp = GetTimestamp(); | |
| return new Split(sw, 0); | |
| } | |
| [MethodImpl(MethodImplOptions.NoInlining)] | |
| private static void ThrowOverflow() | |
| { | |
| throw new InvalidOperationException("Stopwatch cannot contain more than ushort.MaxValue entries"); | |
| } | |
| private static TimeSpan GetElapsed(long start, long stop) | |
| { | |
| var elapsed = stop - start; | |
| if (Stopwatch.IsHighResolution) | |
| { | |
| // convert high resolution perf counter to DateTime ticks | |
| double dticks = elapsed; | |
| dticks *= tickFrequency; | |
| return new TimeSpan(unchecked((long)dticks)); | |
| } | |
| else | |
| { | |
| return new TimeSpan(elapsed); | |
| } | |
| } | |
| private static long GetTimestamp() | |
| { | |
| return Stopwatch.GetTimestamp(); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment