Skip to content

Instantly share code, notes, and snippets.

@cameronism
Created March 12, 2015 03:58
Show Gist options
  • Select an option

  • Save cameronism/22cbf273dee9fc8782fc to your computer and use it in GitHub Desktop.

Select an option

Save cameronism/22cbf273dee9fc8782fc to your computer and use it in GitHub Desktop.
Low overhead nested stopwatch for .NET
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