Click here to Skip to main content
Click here to Skip to main content
Go to top

Easy to use Performance Testing Component

, 19 Jul 2004
Rate this:
Please Sign up or sign in to vote.
A component to enable performance testing and timing of code in .NET.

Visual Studio Screenshot

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:

  1. Be able to time code to a resolution of a few microseconds.
  2. 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:

  1. The RDTSC instruction: This is available on all Pentium and Athlon processors.
  2. 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:

  1. Reset(): This resets the count to 0 and can be called anytime.
  2. 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);
    //...trace compensation needed here!
}

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); //Take account of the trace statement

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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jonathan Nethercott
Software Developer (Senior) CodeWrite Ltd.
United Kingdom United Kingdom
Jon is a Software engineer with over 25 years of experience, the last 9 of which have been using C# and ASP.NET. Previously he has used C++ and MFC. He has a degree in Electronic Systems Engineering.

Comments and Discussions

 
QuestionEvaluating Performance Pinmemberlowmodelrs24-Jan-07 4:08 
AnswerRe: Evaluating Performance PinmemberJon Nethercott25-Jan-07 1:01 
GeneralRe: Evaluating Performance Pinmemberlowmodelrs25-Jan-07 6:54 
GeneralA few suggestions... PinmemberStealthyMark12-Aug-04 1:56 
Good article. Though, the public interface could use some improvement.
 
First, let's look at the typical usage of your class.
void TimeStuff()
{
    // Instantiate a timer
    StopWatch timer = new Stopwatch();
 
    // Start the timer
    timer.Reset();
 
    // Do some stuff
    DoStuff();
 
    // Print the timing to the trace log.
    timer.Trace("Stuff done.");
}
 
As you can see, void Reset() should be renamed to void Start(), because the method is actually used to (re)start the timer.
 
The property double Timer_us clearly shows multiple errors. Because you use a non-specific property type, you have to a) resort to weird naming and b) specify the unit of time in the name. A better signature would be TimeSpan Current. Why Current? Because the property returns the elapsed time since start.
 
void Trace() and void Trace(string msg) should also return the elapsed time. The signatures should be changed to TimeSpan Trace() and TimeSpan Trace(string msg), respectively.
IMO, you should avoid writing to the trace log (or anything else) altogether. Just let the user decide what to do with the returned timing information. Maybe he wants to compute something like bytes/sec and show that on a statusbar? I'd just override the string ToString() method to provide a formatted string of the current time.
 
And btw, the is no reason not to use µs instead of us Wink | ;)
GeneralRe: A few suggestions... PinmemberJon Nethercott17-Aug-04 8:05 
GeneralFollow the aricle guidelines Pinmembernorm.net21-Jul-04 6:13 
GeneralRe: Follow the aricle guidelines PinmemberTom Archer21-Jul-04 10:58 
GeneralRe: Follow the aricle guidelines PinmemberAllen Anderson21-Jul-04 13:56 
GeneralRe: Follow the aricle guidelines Pinmembernorm.net21-Jul-04 20:59 
GeneralRe: Follow the aricle guidelines PinmemberAllen Anderson26-Jul-04 5:27 
GeneralRe: Follow the aricle guidelines Pinmembernorm.net21-Jul-04 20:58 
GeneralRe: Follow the aricle guidelines PinmemberTom Archer22-Jul-04 1:21 
GeneralRe: Follow the aricle guidelines Pinmembernorm.net22-Jul-04 2:22 
GeneralRe: Follow the aricle guidelines Pinmembercomputerguru9238217-Dec-05 7:08 
GeneralRe: Follow the aricle guidelines Pinmembernorm.net19-Dec-05 20:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141002.1 | Last Updated 20 Jul 2004
Article Copyright 2004 by Jonathan Nethercott
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid