Features


A C++ Chronograph Class

Greg Messer

Here's a class for timing program intervals that's powerful in its simplicity.


A Programming Problem Solved

One coding task I find myself doing over and over in my programs is timing. I time the program as a whole, I time the individual phases of the program, I time the I/O throughput, and I estimate the program's completion time. That timing code is just complicated enough to make me think carefully while writing it -- and just tedious enough to make me want to never write it again. What I need is an easily reusable, plug-in tool to simplify those timing tasks for me.

To fill that need I have created a C++ Chronograph class, a software stopwatch to handle my repetitive timing requirements. The Chronograph class provides the same functionality as a good digital stopwatch. It will give the elapsed time, split times, and lap times. You can start, stop, restart, and reset the Chronograph. It can break out the hours, minutes, and seconds for you. Best of all, several Chronograph's can run at the same time without slowing down your program one bit.

Chronograph Functionality

The Chronograph can be in one of two modes, running or stopped. Chronograph::start does one of two things, depending on whether the Chronograph is running or stopped. When running, Chronograph::start will restart the Chronograph at zero and leave it running. When stopped, it restarts the Chronograph from zero. I coded Chronograph::start to work this way because that is how a stopwatch works. The return value of this member function is the elapsed time.

Calling Chronograph::elapsed or Chronograph::split gets the elapsed time of execution up to the current moment. The elapsed time is the amount of time since the Chronograph was started. Splits are the same as the elapsed time, so function split just calls elapsed.

Chronograph::elapsedHMS breaks down the elapsed time into hours, minutes, and seconds. Using this function allows you to report "5 hours, 32 minutes, 10.25 seconds" instead of "19930.25 seconds".

Laps are different from the simpler elapsed time. If you call Chronograph::lap it will return the amount of time since Chronograph::lap was last called. Laps are the periodic intervals that exist within a program. Like a runner doing laps on a track, programs loop and perform other operations that have distinct starting and stopping moments. For example, you can keep track of the time spent processing 1,000 database records, so you can estimate the program's completion time, or keep track of the time spent in each phase of a multi-phase process.

Chronograph::stop puts the Chronograph in the stopped mode. The Chronograph stores the clock value when it was stopped and uses it to adjust the start time at the next restart. The amount of time spent in the stopped mode does not count toward the elapsed time. The return value of this member function is the elapsed time. If you call one of the elapsed member functions Chronograph::elapsed or Chronograph::elapsedHMS while the Chronograph is stopped, you will get the elapsed time when the Chronograph was stopped.

I have provided Chronograph::isstopped to indicate whether the Chronograph is stopped. It returns 1 if the Chronograph is stopped, and 0 if it is running. I recommend changing this member function to use the new bool type when it becomes available with your compiler.

Implementation Details

How can you have several timers going at once and not slow down your program? Well, you don't do it by installing hardware interrupt handling routines, although those might provide a higher resolution. You give up some resolution by simply using the timer that is already in place -- the system clock.

The ANSI C clock Function

The Chronograph class uses the ANSI C clock function to get the time. The clock function returns a value of the type clock_t, which is the number of clock ticks since a program started. To calculate the number of seconds represented by those clock ticks, divide the clock_t value by the ANSI C macro CLOCKS_PER_SEC.

The CLOCKS_PER_SEC macro will likely be different from platform to platform, but the equations that use it will remain the same. The CLOCKS_PER_SEC macro in Borland C is defined as 1000.0. Borland's clock function therefore returns the thousandths of a second since a program started. The MS-DOS system clock that Borland C uses in its clock function is not really accurate to the thousandth of a second, but the Chronograph class is not meant to be that accurate either. The MS-DOS system clock is more than accurate enough for the long-duration timing tasks I had in mind.

The operating system maintains a clock tick counter at all times, and the initial value of that counter is stored by the startup code before main is called. Subsequent calls to clock query the OS clock value, then subtract the value stored at startup. The result is the number of clock ticks since the program started.

This method of time measurement has little effect on your program's execution speed, regardless of how many Chronographs you have instantiated. The only time the Chronograph affects the speed of your program is when you call one its member functions. The OS is servicing the clock ticks for you, whether you use them or not.

Simple Calculations

The simple calculations used by the Chronograph class depend on a few private variables. By keeping track of the clock values when the Chronograph was instantiated, when the Chronograph was stopped, and when the lap member function was last called, the Chronograph calculates elapsed and lap times as the difference between two clock values.

A private member function, Chronograph::diff, accepts two clock_t values and returns the difference between them in seconds. This function is used whenever the elapsed or lap times need to be calculated.

Handling the Modes

As mentioned earlier, the Chronograph can be in one of two modes, running or stopped. If the Chronograph is ever stopped, it can be restarted or it can be reset and then started from zero. The elapsed and lap times can be queried regardless of the Chronograph mode. Being able to stop the Chronograph allows you to keep sections of code from being timed. For instance, you can stop the Chronograph while waiting for user input, then restart it when your program goes back to work. The time spent waiting for the user will not affect your timing statistics.

A Simple Demonstration

I show part of a filter program to demonstrate the use of the Chronograph class. The program is named GETCMT. It reads a C or C++ source file from the standard input stream and writes the comments to the standard output stream. I use this utility to give me a starting point for documenting my source code.

A simple example of the Chronograph use occurs in the main function of GETCMT.CPP (Listing 1) . The main function uses this Chronograph to time the entire program. The Chronograph is instantiated a few lines down from main's parameter list. It hits the ground running, so to speak. You don't need to call any member functions to start it. The program takes the elapsed time near the end of main, after calling the function getcmt. Those two lines are all that are required to time an entire program using the Chronograph.

Use setprecision(2) to set up your output stream before you display the elapsed time returned by the Chronograph member functions. If you use printf for output, then use "%.2f" as your format string.

A Complex Use of the Chronograph

The getcmt function demonstrates a more complex use of the Chronograph class. This function uses three Chronographs -- one to time the function as a whole, one to measure the time spent inside of comments, and one to measure the time spent outside of comments. Listing 2 shows portions of getcmt, with representative Chronograph member function calls.

The three Chronographs are instantiated after creation of an I/O buffer and some auto variables. Since getcmt has yet to encounter any comments upon startup, the inside Chronograph is reset to zero shortly after instantiation, and it remains stopped. The two comment Chronographs (inside and outside) stop and start when getcmt's scanning passes into and out of comments.

When getcmt detects the start of a comment it executes the following statements:


outside.stop();
inside.start();

Likewise, when getcmt detects the end of a comment, it executes the following statements:


inside.stop();
outside.start();

That is all the code needed to maintain those two Chronographs. The simplicity of the interface is what allows you to easily add one or more Chronographs to an existing program.

getcmt stops all three Chronographs just after the end of its main while loop. Calling Chronograph::stop stops a Chronograph and also returns elapsed time.

Although not shown here, the full source code version of getcmt calls member function Chronograph::elapsedHMS to get the total time broken out as hours, minutes, and seconds. The sample program most likely will not need to measure hours and minutes, but I have some programs in daily production that use the long-range capabilities of the Chronograph. The implementation for elapsedHMS is available electronically with the full source code (see p. 3).

Other Uses for the Chronograph

The Chronograph has some other uses besides simple time measurement. For example, let's say you are using Chronograph::lap to track how long you spend on some cyclical aspect of your program, such as processing database records. If you know the total number of those records in advance you can call Chronograph::lap after each 1,000 records and use those lap times to dynamically update an estimated completion time.

I also put the Chronograph to good use in a "spinner" class that I wrote to indicate a program's progress. By repeatedly cycling through a series of characters, ('/', '-', '\', and '|'), I display a character that appears to be spinning.

The spinner class contains a private Chronograph that makes sure the screen update does not happen too often. No matter how frequently an application tries to update the spinner, it only displays itself at one second intervals.

Conclusion

I use the Chronograph class daily. Often I am asked how long it will take to execute some program. With as little as two lines of code I can add a Chronograph to any C++ program and get the timing statistics I need. With only a few more lines of code I can create any timing statistic I can imagine.

The need for timing exists in virtually every significant program. With this Chronograph, you can add timing capabilities in a few moments and then focus on the more interesting parts of the program. That's the real power of a great tool.

Listing 3

Greg Messer works for Systems Integration and Imaging Technology, Inc. as a programmer/analyst and system administrator. He started programming 12 years ago on mainframes and now specializes in high performance client/server programming on networked PCs. Greg can be reached by e-mail at 73510.1430@compuserve.com.