Download PerformanceCounter.zip Assembly level developers have long enjoyed the use of RDTSC (read time stamp counter) instruction to achieve high resolution (1/clock speed (HZ)). And C++ developers have also been using QueryPerformanceCounter and QueryPerformanceFrequency from the Windows API to achieve the same level of resolution. In .Net, we can use these two Windows API functions as well (see How To Use QueryPerformanceCounter to Time Code in Visual C# .NET), but the interopped calls from the .Net framework seem to have some latency and thus affect the possible resolutions obtained using the API functions. Also, the call is not particularly convenient to make (it has a reference type parameter).
One quick solution is to wrap the API function within a class, but this unfortunately adds more overhead to the call and thus reduces the achievable accuracy even future. I tried to address the convenience and accuracy issues by providing a customized class. It enhances the accuracy by measuring the average overhead associated with the call. This is shown as follows (in the constructor):
public PerformanceCounter() {
int r = QueryPerformanceCounter(ref counterStart);
if (r == 0) throw new Exception("High-resolution counter not supported.");
Start();
End();
for (int i = 0 ; i < 100000 ; i++) {
Start(); avgOverHead += End();
}
avgOverHead = (long) ((double) avgOverHead / 100000.0);
}
public void Start() {
QueryPerformanceCounter(ref counterStart);
}
public long End() {
QueryPerformanceCounter(ref counterEnd);
return counterEnd – counterStart;
}
Every time when the class is initialized, the API function is called continuously for 100000 times and the average call overhead is obtained. A function TimeElapsed is provided to calculate the time span between the Start() and the End() call. Note that the first round of the Start() and End() call is ignored because it typically takes longer for the CLR to load the code for the first time.
public double TimeElapsed(string unit) {
double diff = counterEnd – counterStart – avgOverHead;
double seconds = diff / ((double) Frequency);
switch (unit.Trim().ToUpper()) {
case "S": return seconds;
case "MS": return seconds * 1000.0;
case "US": return seconds * 1000000.0;
case "NS": return seconds * 1000000000.0;
default: throw new Exception("Unit " + unit + " not supported.");
}
}
The TimeElapsed function takes the call latency into consideration. It also simplifies the result by converting it to the specified measurement unit (second, millisecond, etc). The following code sample shows how to use the utility:
PerformanceCounter p = new PerformanceCounter();
p.Start();
{code to be timed goes here}
p.End();
Console.WriteLine(p.TimeElapsed("ms"));