Links to other parts:
- Ready, Set, Allocate! (Part 1)
- Ready, Set, Allocate! (Part 2)
- Ready, Set, Allocate! (Part 3)
- Ready, Set, Allocate! (Part 4)
- Ready, Set, Allocate! (Part 6)
In this part I will go over a simple high performance timer class. I’m sure there are plenty of other examples out there that are similar, especially since I put this together by looking at what others did on the web. So first off, thank you to everyone who helped me knowingly or otherwise.
The system used for development and testing is an old laptop running Windows XP Pro Service Pack 3, with a Pentium-M 1.6GHz 32-bit processor, 400MHz FSB, and 512MB of RAM.
Two things of note, the following requires the machine to support a high-resolution performance counter, and contradictory to everything else I’ve done up to this point, the timer code is platform specific. The class uses the _LARGE_INTEGER union from WinNT.h, and calls the QueryPerformanceCounter and QueryPerformanceFrequency methods from WinBase.h; my reasoning behind choosing this is that those methods were written to perform exactly the task at hand and optimized for the platform I’m testing on.
The timer keeps the total time since the last time it was started, and subtracts the total amount of time spent paused to return the current amount of time tracked. The timer class is minimalistic, it contains four data members, and in addition to a constructor it has only four methods. The data members keep the frequency of the high-resolution performance counter, the number of ticks when the timer was started or last paused and a running total of all ticks spent while paused. The four methods start/reset, pause or resume the timer, and return the current time as hours, minutes and seconds, including the fractional part of the seconds.
class CHighPerfTimer { protected: _LARGE_INTEGER* m_pTicksPerSecond; // The counter's frequency _LARGE_INTEGER* m_pStartTicks; // The starting point in ticks _LARGE_INTEGER* m_pPausedTicks; // The amount of ticks spent when the timer was last paused _LARGE_INTEGER* m_pTotalPausedTicks; // The amount of ticks spent paused in total public: CHighPerfTimer(void); void Start(void); void Pause(void); void Resume(void); void GetTime(s32& hours, s32& minutes, double& seconds); };
1. Initialize the timer.
The constructor initializes the data members, and then determines the frequency of the high-resolution performance counter on the machine. The frequency of the high-resolution performance counter varies from machine to machine, but will not change while a machine is running, so it must be obtained to be able to determine the meaning of the values returned from QueryPerformanceCounter, with respect to time.
CHighPerfTimer::CHighPerfTimer(void) { m_pTicksPerSecond = new LARGE_INTEGER(); m_pStartTicks = new LARGE_INTEGER(); m_pPausedTicks = new LARGE_INTEGER(); m_pTotalPausedTicks = new LARGE_INTEGER(); m_pTicksPerSecond->QuadPart = 0; m_pStartTicks->QuadPart = 0; m_pPausedTicks->QuadPart = 0; m_pTotalPausedTicks->QuadPart = 0; // Get the high resolution counter's frequency QueryPerformanceFrequency(m_pTicksPerSecond); }
2. Starting the timer.
Start initializes the timer and begins the counting of ticks. The QueryPerformanceCounter call obtains the number of ticks reported by the system’s high-resolution performance counter, and can be compared against a later call to QueryPerformanceCounter, so that counting need only be done internally by the system’s high-resolution performance counter.
void CHighPerfTimer::Start(void) { // Reset the Paused ticks count. m_pPausedTicks->QuadPart = 0; m_pTotalPausedTicks->QuadPart = 0; // Get the starting ticks. QueryPerformanceCounter(m_pStartTicks); }
3. Pausing the timer.
Pause starts counting ticks while the timer isn’t considered as running, by getting the current number of ticks for comparison against a later call to QueryPerformanceCounter.
void CHighPerfTimer::Pause(void) { // Start the paused ticks count. QueryPerformanceCounter(m_pPausedTicks); }
4. Resuming a paused timer.
Resume ends the counting of ticks since Pause was called and adds the difference of ticks between calls to Pause and Resume to the total number of ticks spent while paused. If the timer hasn’t previously been started, then resume will start the timer.
void CHighPerfTimer::Resume(void) { if (m_pStartTicks->QuadPart != 0) { // End the paused ticks count. LARGE_INTEGER endPausedTicks; QueryPerformanceCounter(&endPausedTicks); if (endPausedTicks.QuadPart > m_pPausedTicks->QuadPart) m_pTotalPausedTicks->QuadPart += endPausedTicks.QuadPart - m_pPausedTicks->QuadPart; m_pPausedTicks->QuadPart = 0; } else { Start(); } }
5. Reporting the tracked time of the timer.
GetTime gets the current number of ticks from the high-resolution performance counter, converts the difference between the current number of ticks and the starting number of ticks, minus the total ticks spent paused, into a time value by dividing by the frequency, and then separates the time into hours, minutes and seconds, including the fractional part of the seconds.
void CHighPerfTimer::GetTime(s32& hours, s32& minutes, double& seconds) { LARGE_INTEGER ticks; double time; // Get the latest tick count. QueryPerformanceCounter(&ticks); // If the timer is paused, discount the time since it was paused. LARGE_INTEGER immediatePausedTicks = *m_pTotalPausedTicks; if (m_pPausedTicks->QuadPart > 0) immediatePausedTicks.QuadPart += ticks.QuadPart - m_pPausedTicks->QuadPart; // Convert the elapsed ticks into number of seconds time = (double)(ticks.QuadPart - immediatePausedTicks.QuadPart - m_pStartTicks->QuadPart)/(double)m_pTicksPerSecond->QuadPart; // Number of hours hours = (s32)time/3600; // Number of minutes time = time - (hours * 3600); minutes = (s32)time/60; // Number of seconds seconds = time - (minutes * 60); }
Next time I plan to cover the results of my comparisons between this malloc and the built-in malloc.