Home > Guides > Programming > .NET and Windows Programming

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

High Resolution Timing

Last updated May 13, 2004.

If you need better than 10 millisecond resolution, you have to look outside of the .NET Framework classes. Windows provides a high-resolution timer that can give sub-millisecond resolution, but to get to it you have to call a Windows API function through runtime interop services.

The Windows high-performance timer interface consists of two functions: QueryPerformanceCounter and QueryPerformanceFrequency. QueryPerformanceCounter returns the current performance counter value in a 64-bit integer. You can use this function in the same way as you use the other timers: get the starting value, perform an operation, and then subtract the starting value from the ending value. That gives you the difference in performance counter values, but it doesn't provide a time reference. The time reference is supplied by QueryPerformanceFrequency. This function returns the performance frequency—the number of performance counter values per second. So the number of elapsed seconds is:

(ending_value – starting_value) / frequency

For example, on my system QueryPerformanceFrequency returns 3579545. So each "tick" is 1/3579545 second, or about 0.28 microseconds. Sub-microsecond timing, what a deal! Well, almost.

Whereas it's true that QueryPerformanceCounter will give you sub-millisecond resolution, your accuracy is limited by the amount of time it takes to obtain the counter value. For example, on my 750 MHz Pentium it takes almost four microseconds per call to QueryPerformanceCounter. Since it takes two calls (one for the start value and one for the stop value), the overhead works out to eight microseconds. Still, that's 100 times more accurate than Environment.TickCount, and most likely good enough for most things that you'll be working with.

The C prototypes for the two high-resolution timer functions are:

BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);

Both functions return a non-zero value if a high-resolution performance counter. If no such counter is installed, the functions return zero. The single parameter is a pointer to a 64-bit value in which the function returns the counter frequency or counter value. The equivalent C# and Visual Basic function definitions are:

[C#]

public static extern bool QueryPerformanceCounter(out long perfcount);
public static extern bool QueryPerformanceFrequency(out long freq);

[Visual Basic]

Public Shared Function QueryPerformanceCounter(ByRef perfcount As Int64) As Boolean
Public Shared Function QueryPerformanceFrequency(ByRef freq As Int64) As Boolean

In order to use these functions from a .NET program, you need to create a class that exposes the Windows API calls through runtime interop services. I created a class called PerfCount, shown here:

[C#]

public class PerfCount
{
 [System.Runtime.InteropServices.DllImport("Kernel32.dll")]
 public static extern bool QueryPerformanceCounter(out long perfcount);

 [System.Runtime.InteropServices.DllImport("Kernel32.dll")]
 public static extern bool QueryPerformanceFrequency(out long freq);

 public static long QueryPerformanceCounter()
 {
  long perfcount;
  QueryPerformanceCounter(out perfcount);
  return perfcount;
 }

 public static long QueryPerformanceFrequency()
 {
  long freq;
  QueryPerformanceFrequency(out freq);
  return freq;
 }
}

[Visual Basic]

Public Class PerfCount
 <System.Runtime.InteropServices.DllImport("Kernel32.dll")> _
 Public Shared Function QueryPerformanceCounter(ByRef perfcount As Int64) As Boolean

 End Function

 <System.Runtime.InteropServices.DllImport("Kernel32.dll")> _
 Public Shared Function QueryPerformanceFrequency(ByRef freq As Int64) As Boolean

 End Function

 Public Shared Function QueryPerformanceCounter() As Int64
  Dim perfcount As Int64
  QueryPerformanceCounter(perfcount)
  Return perfcount
 End Function

 Public Shared Function QueryPerformanceFrequency() As Int64
  Dim freq As Int64
  QueryPerformanceFrequency(freq)
  Return freq
 End Function
End Class

You'll notice that the PerfCount provides overloaded versions of the functions that return the value directly rather than requiring you to pass an int64 parameter. It just seems more natural to write:

long freq = PerfCount.QueryPerformanceFrequency();

rather than the Windows API-ish:

Dim freq As Int64
PerfCount.QueryPerformanceFrequency(freq)

Once you've included this class in your project, you can use it to time operations in much the same way that the DateTime.Now and Environment.TickCount timers are used. The only difference is that you need to divide the elapsed tick count by the performance frequency in order to obtain the time in seconds. For example:

[C#]

long startCount = PerfCount.QueryPerformanceCounter();
DoSomething();
long stopCount = PerfCount.QueryPerformanceCounter();
long elapsedCount = stopCount - startCount;
double elapsedSeconds = (double)elapsedCount/PerfCount.QueryPerformanceFrequency();
Console.WriteLine("{0} seconds", elapsedSeconds);

[Visual Basic]

Dim startCount As Int64 = PerfCount.QueryPerformanceCounter()
DoSomething()
Dim stopCount As Int64 = PerfCount.QueryPerformanceCounter()
Dim elapsedCount = stopCount - startCount
Dim elapsedSeconds As Double = elapsedCount / PerfCount.QueryPerformanceFrequency()
Console.WriteLine("{0} seconds", elapsedSeconds)

You could extend the PerfCount class to include methods that convert an elapsed counter value to seconds, milliseconds, microseconds, or whatever time scale that you want.

Half A Solution

As you can see, there are many ways to get an elapsed time value for a particular piece of code. Which one you use depends mostly on how accurate you want to get. Regardless of the way you obtain elapsed time values, you've only solved half of the problem. The other half of the problem is reporting those values so that you can use them to analyze your application's performance. In the examples here, I've just been writing the values to the console. You could write them to the application's event log or add them to Trace output if you wanted. However you do it, you'll then need to create tools that extract the timing information from the logs. However you do it, it's a big job.

Remember also that elapsed processing time doesn't tell you much about your bottlenecks, even if you could time every line of code in the program. Even if you could identify the exact line of code that's causing the performance problem, that doesn't tell you what the problem is. Is the program CPU bound? Is disk I/O taking too long? Is there some weird network latency that's causing your program to hang? Those are but a few of the things that could be causing performance problems.

Elapsed time values only tell you how long something takes. That's very useful when you're trying to optimize a CPU intensive task. But when you add network traffic, database access, and the many other functions that applications perform, the only use of an elapsed time value is comparison: is this version faster than the previous one? If you want to do detailed performance analysis, you need to step into the world of performance counters.

Discussions

Copies of the array?
Posted Dec 23, 2008 03:40 PM by luige21
1 Replies
Hi
Posted Dec 5, 2008 05:10 AM by ajay2000bhushan
2 Replies
You have no clue.
Posted Jun 10, 2008 03:28 PM by theinternetmaster
1 Replies

Make a New Comment

You must log in in order to post a comment.

Related Resources

Jim Mischel"Highly unlikely" does not mean "impossible"
By Jim MischelJuly 18, 2009 No Comments

One of my programs crashed the other day in a very unexpected place.  A call to System.Threading.ConcurrentQueue.TryDequeue (from the Parallel Extensions to .NET) resulted in an OverflowException being thrown.  Investigation revealed a pretty serious bug in the System.Random constructor.

It's Here; Put Away Your Pre-Conceptions on What an OS Must Be: Part II
By John TraenkenschuhMay 24, 2009 No Comments

In the last blog in this series, Traenk relates his first experiences with computers and with coding.  But now, some years have passed. . .

It's Here; Put Away Your Pre-Conceptions on What an OS Must Be: Part I
By John TraenkenschuhMay 24, 2009 No Comments

Traenk relates his past experience with Operating Systems that goes back 25 years, ok, more than that but he ain't tellin'

See More Blogs

Informit Network