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