Overview
This article describes a timer component that allows timing to be done to an accuracy of microseconds.
Introduction
One of the tasks in software development that is often overlooked is performance testing. Most of the time, code is designed to run fast and then tested to make sure that it really is. Whilst there are many ways to test performance and identify slow bits of code, it is quite often difficult to precisely locate or time, specific sections of the code. Using normal time functions, including timer tick functions is only accurate to milliseconds at best. Also, you don't want to have to copy library classes or lots of code around in order to simply time a piece of code. Because of this, I decided to write a general purpose timer that would:
- Be able to time code to a resolution of a few microseconds.
- Be very easy to use.
Background
In order to have a timer that has a resolution in the order of microseconds, there are 2 possibilities that I am aware of:
- The
RDTSC
instruction: This is available on all Pentium and Athlon processors.
QueryPerformanceCounter
: This is a Windows API call to a performance counter which is generally running at speeds well in excess of 1MHz.
Although the first option is the most accurate and fastest, I decided to use the second option because that is much more portable. QueryPerformanceCounter
is supported on all Windows platforms, including Pocket PC devices.
The second requirement was for the timer to be very easy to use. It was fairly obvious to me that the timer should be a strongly named class so that it could be put in the GAC. This would make it very easy to include in an assembly. I also felt that it would be even easier to use if it was a component. That way, if timing was being done on a form (Windows or Web), the component could be dragged from the toolbox onto the page.
Public Methods in the StopWatch Component
The component is very simple to use and has 2 main methods:
Reset()
: This resets the count to 0 and can be called anytime.
Trace()
: This has 2 overloads. One takes no parameters, and just displays the elapsed time in the debug output window. The other takes a string as a parameter, and displays the string before displaying the time. The time is displayed as either microseconds (us), milliseconds (ms), or seconds (s), depending on the elapsed time.
Implementation
The code relies on the two API calls:
QueryPerformanceFrequency
QueryPerformanceCounter
These are defined in KERNEL.DLL on Windows platforms, and CoreDll.dll on the Pocket PC, and are called using P/Invoke as follows:
#if NET_CF
[System.Runtime.InteropServices.DllImport("CoreDll.dll")]
#else
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
#endif
private static extern int QueryPerformanceFrequency(ref Int64 lpFrequency);
#if NET_CF
[System.Runtime.InteropServices.DllImport("CoreDll.dll")]
#else
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
#endif
private static extern int QueryPerformanceCounter(ref Int64 lpPerformanceCount);
NET_CF
should be defined in the build settings if the library is going to be used on the Pocket PC.
The component contains a property called Time_us
which is read-only and returns the elapsed time as follows:
public double Timer_us
{
get
{
QueryPerformanceCounter(ref m_LastCount);
Int64 Count = m_LastCount;
Count -= m_TimerStartCount;
return (double)Count / (double)m_TimerFreq * 1000000.0;
}
}
The Reset
and Trace
methods are implemented as follows:
public void Reset()
{
QueryPerformanceFrequency(ref m_TimerFreq);
QueryPerformanceCounter(ref m_TimerStartCount);
}
public void Trace(string msg)
{
double t1 = Timer_us;
Int64 c1 = m_LastCount;
StringBuilder s1 = new StringBuilder();
if (t1 < 1000)
s1.AppendFormat("{0} Time = {1} us", msg, t1.ToString("F2"));
else if (t1 < 1000000)
s1.AppendFormat("{0} Time = {1} ms", msg, (t1/1000).ToString("F2"));
else
s1.AppendFormat("{0} Time = {1} s", msg, (t1/1000000).ToString("F2"));
System.Diagnostics.Trace.WriteLine(s1);
}
The Trace.WriteLine
statement takes a considerable time to execute (milliseconds). As the code is at the moment, this would seriously affect the displayed times. In order to try and compensate for this, I decided to get the time after the Trace
statement has executed and subtract this from the start time. This means that if you call Trace
continuously, you will get times that are different by about 1 or 2 us instead of times that are different by milliseconds. Although this means the displayed time is not a true indication of the elapsed time, I felt that this behavior was more useful. Unfortunately, I could not reliably compensate for the QueryPerformanceCounter
call, so continuous calls to Trace
resulted in increasing times of about 1.4us on my PC. Most of this time is because of the P/Invoke overhead. The code for this is shown below:
double t2 = Timer_us;
Int64 c2 = m_LastCount;
m_TimerStartCount += (c2-c1);
The final part of the implementation is the deployment of the component. I wrote a batch file (as part of the build process) to copy the assembly to the Visual Studio directory and install it in the global assembly cache. This way, it can easily be added to the References and Component Toolbox. (In the Add References dialog, look for "Nethercott.Timing". And in the Add/Remove Items dialog in the Toolbox, look for "StopWatch".) Obviously, the component only has to be added once to the toolbox (e.g., under the Components tab) in order to be used on multiple solutions.
Using the Component
The component is very easy to use. There are two ways that it can be included. The easiest way is to drag the component from the toolbox onto a Windows or Web Form. The Reset()
and Trace()
methods can then be called as required in the code behind page. The other way to use the component is to include the component manually. This means adding a reference to the class, constructing the StopWatch
object, and then calling the Reset()
and Trace()
methods as required. Although not absolutely necessary, it's probably a good idea to call Dispose()
when the object is no longer required.
References
There are several other CodeProject articles on timing, and timer classes. Here are some of them:
History
- 19-Jul-2004 - Initial version.