Click here to Skip to main content
15,868,340 members
Articles / Mobile Apps
Article

Easy to use Performance Testing Component

Rate me:
Please Sign up or sign in to vote.
4.66/5 (28 votes)
19 Jul 2004CPOL5 min read 86.7K   1.5K   39   16
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:

C#
#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:

C#
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:

C#
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:

C#
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)


Written By
Software Developer (Senior) CodeWrite Ltd.
United Kingdom United Kingdom
Jon is a Software engineer with over 30 years of experience, the last 18 of which have been using C# and ASP.NET. Previously he has used C++ and MFC. He has a degree in Electronic Systems Engineering and is also a fully licensed radio amateur (M0TWM).

Comments and Discussions

 
GeneralMy vote of 5 Pin
majid torfi31-Oct-14 23:00
professionalmajid torfi31-Oct-14 23:00 
QuestionEvaluating Performance Pin
lowmodelrs24-Jan-07 4:08
lowmodelrs24-Jan-07 4:08 
I've been using this timer to evaluate performance. The first time the code runs it is very slow ... around 2 seconds. This is after compiling the code and running it for the first time in debug mode. After that the time it takes to add a transaction to the database decreases to around 60-80 milliseconds (ms) and stays there.

Is there a way to evaluate acceptable tolerances? Who is to say what is fast enough?

Cool piece of code, thanks for writing the article.
AnswerRe: Evaluating Performance Pin
Jonathan Nethercott25-Jan-07 1:01
professionalJonathan Nethercott25-Jan-07 1:01 
GeneralRe: Evaluating Performance Pin
lowmodelrs25-Jan-07 6:54
lowmodelrs25-Jan-07 6:54 
GeneralA few suggestions... Pin
StealthyMark12-Aug-04 1:56
StealthyMark12-Aug-04 1:56 
GeneralRe: A few suggestions... Pin
Jonathan Nethercott17-Aug-04 8:05
professionalJonathan Nethercott17-Aug-04 8:05 
GeneralFollow the aricle guidelines Pin
NormDroid21-Jul-04 6:13
professionalNormDroid21-Jul-04 6:13 
GeneralRe: Follow the aricle guidelines Pin
Tom Archer21-Jul-04 10:58
Tom Archer21-Jul-04 10:58 
GeneralRe: Follow the aricle guidelines Pin
Allen Anderson21-Jul-04 13:56
Allen Anderson21-Jul-04 13:56 
GeneralRe: Follow the aricle guidelines Pin
NormDroid21-Jul-04 20:59
professionalNormDroid21-Jul-04 20:59 
GeneralRe: Follow the aricle guidelines Pin
Allen Anderson26-Jul-04 5:27
Allen Anderson26-Jul-04 5:27 
GeneralRe: Follow the aricle guidelines Pin
NormDroid21-Jul-04 20:58
professionalNormDroid21-Jul-04 20:58 
GeneralRe: Follow the aricle guidelines Pin
Tom Archer22-Jul-04 1:21
Tom Archer22-Jul-04 1:21 
GeneralRe: Follow the aricle guidelines Pin
NormDroid22-Jul-04 2:22
professionalNormDroid22-Jul-04 2:22 
GeneralRe: Follow the aricle guidelines Pin
Paul Conrad17-Dec-05 7:08
professionalPaul Conrad17-Dec-05 7:08 
GeneralRe: Follow the aricle guidelines Pin
NormDroid19-Dec-05 20:35
professionalNormDroid19-Dec-05 20:35 

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

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