see also DOSFrequencyDetector

Intro

This is a quirky little program. It uses the rdtsc time stamp instruction as well as the Windows QueryPerformanceCounter() function, which sometimes uses rdtsc. Although I haven't found it on a Microsoft-published website, I did find a Delphi site that said that Windows uses rdtsc on multiprocessor NT systems. I have verified that this is the case on a dual processor Xeon 3 Ghz system. Otherwise, all systems I've tested, from my PIII-850 to Centrino notebooks give a QueryPerformanceFrequency() result of 3579545. I take it this is using a higher resolution hardware timer than the venerable 8254.

UPDATE: I have deduced that a QueryPerformanceFrequency() result of 3,579,545 is a magic number. It is the frequency of the ACPI counter. ACPI is that subsystem used for power management and such that superceded APM. ACPI-compliant systems use timers that run even when the machine is suspended, so there should be no danger of having skew when the processor slows down. Now this is based on my very basic knowledge of ACPI.

If you do QueryPerformanceFrequency() and get 1,193,182, this is the good old 8245 timer chip saying hello. It is unlikely you will ever see this on a modern system.

I found help on rdtsc here and here

the binary

JustinTimer.exe

the code

#include <stdio.h>
#include <windows.h>
#include <time.h>

#define SLEEP_TIME_MS 1000
 
#define cpuid __asm __emit 0fh __asm __emit 0a2h
#define rdtsc __asm __emit 0fh __asm __emit 031h

volatile unsigned __int64 RDTSC()
{   __asm __emit 0fh __asm __emit 0a2h
   __asm __emit 0fh __asm __emit 031h }

int warmup () 
{
   int subtime;


   float x = 5.0f;
   __asm 
   {
   // Make three warm-up passes through the timing routine to make
   // sure that the CPUID and RDTSC instruction are ready
   cpuid
   rdtsc
   mov subtime, eax
   cpuid
   rdtsc
   sub eax, subtime
   mov subtime, eax

   cpuid
   rdtsc
   mov subtime, eax
   cpuid
   rdtsc
   sub eax, subtime
   mov subtime, eax

   cpuid
   rdtsc
   mov subtime, eax
   cpuid
   rdtsc
   sub eax, subtime
   mov subtime, eax 

   // Only the last value of subtime is kept
   // subtime should now represent the overhead cost of the
   // MOV and CPUID instructions

   }
   return subtime;
}

   
void main(void)
{
   LARGE_INTEGER frequency;
   LARGE_INTEGER count1, count2;
   BOOL result;

   int rdtsc_overhead = warmup();
   printf ("warmup time is %lu cycles\n", rdtsc_overhead);


   printf("Doing 'QueryPerformanceFrequency()'\n");
   result = QueryPerformanceFrequency(&frequency);

   if (result)
   {
      printf("It appears this hardward *does* support a high freq counter\n");
      printf("This machine does %I64i ticks per second\n\n", frequency.QuadPart);
   }
   else
   {
      DWORD errorCode = GetLastError(); 
      printf("This hardward *does not* support a high freq counter\n");
      printf("The error code was %u\n", errorCode);
      return;    
   }

   printf("Doing 'QueryPerformanceCounter()'\n");
   result = QueryPerformanceCounter(&count1);

   LONGLONG rd = RDTSC() - rdtsc_overhead;

   if (result)
   {      
      printf("It appears this hardward *does* support a high freq counter\n");
      printf("QueryPerformanceCounter() reports %I64i ticks\n", count1.QuadPart);
      printf("RDTSC reports %I64i ticks\n", rd);
      
      printf("\nNow doing a test - sleeping for around %d ms\n", SLEEP_TIME_MS);
      BOOL done = 0;
      DWORD time1, time2;
      double accuracy;

      time1 = GetTickCount();
      while (!done)
      {
         time2 = GetTickCount();

         if (time2 - time1 > SLEEP_TIME_MS)
         {   
            QueryPerformanceCounter(&count2);
            done = 1;
         }         
      }
      
      LONGLONG rd2 = RDTSC() - rdtsc_overhead;
      LONGLONG predicted, actual;
      predicted = frequency.QuadPart / 1000 * (time2 - time1); 
      actual = count2.QuadPart - count1.QuadPart;
      accuracy = (double)predicted / (double)actual;
      LONGLONG cpu_speed = (rd2 - rd) / 1000 / SLEEP_TIME_MS;
      
      printf("I slept for %d ms\n", time2 - time1);
      printf("I detect CPU speed of %d MhZ\n", cpu_speed);
      printf("%lu ticks went by\n", actual);
      printf("The accuracy is about %lf%% (100%% is perfect)\n", 100 * accuracy);
   }
   else
   {
      DWORD errorCode = GetLastError(); 
      printf("This hardward *does not* support a high freq counter\n");
      printf("The error code was %u\n", errorCode);
      return;    
   }
}

Sample Run - 1.5Ghz Laptop

warmup time is 568 cycles
Doing 'QueryPerformanceFrequency()'
It appears this hardward *does* support a high freq counter
This machine does 3579545 ticks per second

Doing 'QueryPerformanceCounter()'
It appears this hardward *does* support a high freq counter
QueryPerformanceCounter() reports 426961653303 ticks
RDTSC reports 33432009902274 ticks

Now doing a test - sleeping for around 1000 ms
I slept for 1002 ms
I detect CPU speed of 1484 MhZ
3565851 ticks went by
The accuracy is about 100.569485% (100% is perfect)

-- MattWalsh - 03 Jul 2003

Topic revision: r5 - 02 Jan 2006 - MattWalsh
 
This site is powered by the TWiki collaboration platformCopyright © 2008-2012 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback