Welcome to Marlon's place!

Message of the day

"There is a brave man waiting, who'll take me back to where I come from"

(The Black Heart Rebellion)

Measuring code performance in Delphi

posted: Januari 18, 2009

The first step in code optimization is of course knowing which parts of your code you need to optimize. In order to find out these parts we need to focus on, we need a method of timing the code in question. Today, I'd like to offer three simple ways of measuring code performance, in increasing levels of accuracy.

The first method of finding out how long it takes to perform a certain set of instructions is also the easiest: simply keep track of the exact time before and after these instructions are executed. Here's a little example:

procedure TForm1.ButtonClick(Sender: TObject);
  startTime : Double;
  endTime   : Double;
  delta     : Double;
  startTime := Time;
  endTime := Time;
  delta := endTime - startTime;

In this little example, 'DoSomething' is the name of the procedure which contains the code which performance we want to measure. As you can see, it's a very easy method to quickly find out how long DoSomething takes to finish. But there is one big problem with this method: what if DoSomething is a really quick procedure, taking very little time to execute? We still might want to optimize the procedure (for example because we want to call it a lot of times through a loop), but measuring performance in seconds is not a good idea in this case. This brings us to the second method.

The second method relies on using the function GetTickCount, which returns the time in milliseconds, thus increasing the accuracy with a factor of thousand. Neat! The basic principle remains the same, however, as you can see in the following snippet of code:

procedure TForm1.ButtonClick(Sender: TObject);
  startTick  : DWord;
  delta      : DWord;
  startTick := GetTickCount;
  delta := GetTickCount - startTick;
  ShowMessage(IntToStr(delta) + 'ms');

Easy enough - we are now able to measure how long DoSomething takes in milliseconds. The code above is basically the same as the first snippet, using GetTickCount instead of Time, and it's also a bit optimized in itself. There is no need to keep track of the 'end' tick count, since we're not using it outside of the calculation of the delta time. The same applies to our first example, by the way.

Millisecond accuracy should be enough for 99% of the times you'd want to measure performance, but from time to time, you still need to have a greater detail of accuracy. One of the projects I'm working on at the moment, is an RPG game. For this game, we needed a high resolution timer for various parts of the game engine. While we were able to achieve very high frames-per-second figures for the basic renderer itself, we were not able to match this speed in the timer, basically because of the crappy timer resolution in Windows. However, there is still an option left to measure time differences with an ever higher level of accuracy.

The third and final method: QueryPerformanceCounter and QueryPerformanceFrequency. Ok, this method is a little bit more complicated, but it's still easy enough to be used once you get the gist of it. Both functions are functions which are defined within the Windows unit, and are described in the MSDN as follows:

  • QueryPerformanceCounter: the QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter.
  • QueryPerformanceFrequency: the QueryPerformanceFrequency function retrieves the frequency of the high-resolution performance counter, if one exists. The frequency cannot change while the system is running.

Basically, you can request the value of an internal counter of your CPU, which counts at the frequency of said CPU. High resolution indeed - for example, on my current development machine (a 3.4GHz Pentium IV), the frequency is 3.4 x 10^9, which means the accuracy of this timer is one three-and-a-half billionth of a second! How's that for accuracy? Anyway, time for an example:

procedure TForm1.ButtonClick(Sender: TObject);
  frequency : Int64;
  startTime : Int64;
  endTime   : Int64;
  delta     : Extended;
  delta := (endTime - startTime) / frequency;

The resulting float will return you the time measured in seconds. In order to find out the time difference in more detail, you simply:

  • multiply with 1000 to get milliseconds
  • multiply with another 1000 to get microseconds
  • multiply with another 1000 to get nanoseconds

There are some possible pitfalls with this timer, however: the frequency returned for my machine was exactly the same as the clock speed of my CPU. This is not necessarily the case, so be careful with this, especially on dual-, quad-, and multicore machines. For example, my office machine (a quadcore 2.4GHz) returns a value of around 14 x 10^6 - still a lot more accurate than a GetTickCount call, yet a lot less than my own machine. For more information on these possible pitfalls, I'll refer you to an article called 'Beware of QueryPerformanceCounter', by the author of VirtualDub. Having said that, if you keep these in mind, you'll stil have a very useable high resolution timer on hand whenever you might need it.

In conclusion, some final words of warning on code optimization in general:

  • Don't run any programs in the background, as these can and will influence your findings.
  • Make sure you know what you're measuring: for example if you're measuring a routine reading/writing files, make sure you know what your hard drive's cache is doing, otherwise this will mess up your results.
  • Never ever rely on just one check: perform multiple checks and work with averages, as there are always factors in play you haven't accounted for.
  • Don't over-optimize: readable code, as well as re-usable code is preferred over code which runs just a little bit faster.
  • Don't pre-optimize: even though it's a good idea to always write lean and clean code, there really is no need to get the last millisecond performance out of every procedure and function. You'll lose time throughout your project, time which could have been spent on debugging or more features.

Well, there you have it - three similar yet very different ways to measure time differences: use them wisely...

Previous articles

Recent C++ stuff

Recent Delphi stuff

Recent Java stuff