Page 1 of 2

CPU Frequency MicroSecond time

Posted: Sat Aug 15, 2009 12:38 pm
by idle
Edited

MicroSecond not Nano :lol:

See posts bellow, doesn't work on x64 and probably has issues on multicores and speed stepping processors.

if it has any chance of working it needs to lock the thread with setThreadMaskAffinity to a CPU or Core (though I don't know if that works or not) to try and counter the speed step issues it periodically recalculates the CPU frequency.

Code: Select all


;Attemps to produce a workable Microsecond timer / delay
;I don't have a multicore system or speed stepping cpu so I have no idea if it will be stable
;
;The timer needs to be initalised in the main thread as it will try to lock the thread to a core
;should avoid issues with Time Stamps being out of sync on multi cores
;It also spawns another thread to check the frequency periodically (may need a mutex)
;
;Turn debugger off
;
;set to 0 to test without beginTimePeriod higher accuracy 
EnableExplicit 

#CompareMilliSeconds = 1

CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
#X64 = 0
CompilerElse
#X64 = 1 
CompilerEndIf

CompilerIf #X64  
  Procedure.i CPUTime()
CompilerElse 
  Procedure.q CPUTime()
CompilerEndIf
   !XOr eax, eax
   !cpuid
   !rdtsc
   ProcedureReturn
EndProcedure

Global gthreadIDCheck.i,gtidCheckRun.i=1,bchangeFreq 

CompilerIf #X64
  Global gStartTime.i,gfrequency.i 
  Procedure.i CPUFrequency(intDelayTime.l=500)
CompilerElse 
  Global gStartTime.q,gfrequency.q 
  Procedure.q CPUFrequency(intDelayTime.l=500)
CompilerEndIf 

  Protected TimerHi.l,TimerLo.l,PriorityClass.l,Priority.l
  
  PriorityClass = GetPriorityClass_(GetCurrentProcess_());
  Priority = GetThreadPriority_(GetCurrentThread_());
  SetPriorityClass_(GetCurrentProcess_(), #REALTIME_PRIORITY_CLASS);
  SetThreadPriority_(GetCurrentThread_(), #THREAD_PRIORITY_TIME_CRITICAL);
  Delay(10);

  EnableASM
  XOr eax, eax
  !cpuid
  rdtsc
  mov TimerLo, eax
  mov TimerHi, edx
 
  Delay(intDelayTime);

  XOr eax, eax
  !cpuid
  rdtsc
  sub eax, TimerLo
  sbb edx, TimerHi
  mov TimerLo, eax
  mov TimerHi, edx
  DisableASM

  SetThreadPriority_(GetCurrentThread_(),Priority);
  SetPriorityClass_(GetCurrentProcess_(), PriorityClass);
  
  ProcedureReturn TimerLo / (1000.0 * intDelayTime);

EndProcedure

Procedure CheckFreqency(CheckInterval.i=500)

 While gtidCheckRun
   gfrequency = CPUFrequency(CheckInterval)
   bchangeFreq  = 1 
   Delay(CheckInterval)
 Wend
 
EndProcedure

Procedure KillTimer()
  gtidCheckRun = 0
  If IsThread(gthreadIDCheck)
    WaitThread(gthreadIDCheck)
  EndIf   
EndProcedure 

Procedure InitTimer(CheckInterval.i=500)
  Protected mask.i=1
    
  If Not IsThread(gthreadIDCheck) 
     SetThreadAffinityMask_(GetCurrentThread_(),mask)
     gfrequency = CPUFrequency(CheckInterval)
     gThreadIDCheck = CreateThread(@CheckFreqency(),CheckInterval)
  EndIf     
    
  gStartTime = CPUTime()

  ProcedureReturn gfrequency

EndProcedure

Procedure GetElapsedMicroSeconds()
  CompilerIf #x64
     Protected tt.i,dt.i
  CompilerElse
     Protected tt.q,dt.q
  CompilerEndIf
  
  tt=CPUTime()
  dt = tt-gStartTime 
  If dt > 0
    ProcedureReturn dt / gFrequency
  Else 
    ProcedureReturn 0
  EndIf 
    
EndProcedure

Procedure DelayMicro(mtime.i)
  CompilerIf #x64
    Protected te.i, tn.i
  CompilerElse 
    Protected te.q, tn.q
  CompilerEndIf  
  
  te = GetElapsedMicroSeconds() + (mtime)
  Repeat
    Delay(0)
  Until GetElapsedMicroSeconds() >= te

EndProcedure

;test----------------------------------
;Set #CompareMilliSeconds to 1 to compare MS, higher accuracy without TimeBeginPeriod 
CompilerIf #CompareMilliSeconds 
  Global tt.TIMECAPS
  timeGetDevCaps_(@tt, SizeOf(TIMECAPS)) 
  timeBeginPeriod_(tt\wPeriodMin)
CompilerEndIf 
 
Global sum1.i, sum2.i,ct.i,sta.i,elt.i,mst.i,a,el.i,avg1,avg2

OpenConsole()
Delay(100)

Global mfreq = InitTimer(500)

PrintN(Str(mfreq) + " MHz") 
 
Repeat
  
  sta = GetElapsedMicroSeconds()
  CompilerIf #CompareMilliSeconds 
    mst = timeGetTime_()
  CompilerEndIf 
  a=0
  
  While a < 1000;  should be ~1000 Micro Seconds or 1MS to compleate loop
    DelayMicro(1)
    a+1
  Wend

  elt = GetElapsedMicroSeconds() - sta
  CompilerIf #CompareMilliSeconds 
    el = timeGetTime_() - mst
    PrintN(Str(gfrequency) + " MHz " + Str(elt) + " MicroSeconds " + Str(el) + " MilliSeconds")
  CompilerElse 
    If bchangeFreq 
      PrintN(Str(gfrequency) + " MHz " + Str(elt) + " MicroSeconds " +"frequency bump")
      bchangeFreq = #False 
    Else    
      PrintN(Str(gfrequency) + " MHz " + Str(elt) + " MicroSeconds ")
    EndIf 
  CompilerEndIf
  ct+1
  sum1 + elt
  sum2 + el

Until Inkey()
  
  avg1.i = sum1 / ct
  CompilerIf #CompareMilliSeconds 
    avg2.i = sum2 / ct
    PrintN("Averages " + Str(avg1) + " MicroSeconds " + Str(avg2) + " Milliseconds")
  CompilerElse
    PrintN("Averages " + Str(avg1) + " MicroSeconds ")
  CompilerEndIf  

KillTimer() 
 
Input() 

Re: CPU Frequency

Posted: Sat Aug 15, 2009 1:18 pm
by AAT
My comp, Pentium Dual-Core CPU:
CPU_Z - 3204 MHz
your code - 3471 kHz

Posted: Sat Aug 15, 2009 1:21 pm
by AND51
Intel Pentium D, Dual Core with 2x1.86 GHz=3.4 GHz...
With your code, the Debugger wrote:3392
31238200311146

Posted: Sat Aug 15, 2009 2:19 pm
by milan1612
Doesn't work with Purebasic x64 :(

Posted: Sun Aug 16, 2009 12:00 am
by idle
Turns out accurate on my AMD 2000 mhz, maybe it has some problems problems with multi cores, though I don't know.
but if you need a timer with resolution less than 1 ms then I guess it'd be no worse than the regular timers.

Posted: Sun Aug 16, 2009 12:10 am
by Thorium
idle wrote:Turns out accurate on my AMD 2000 mhz, maybe it has some problems problems with multi cores, though I don't know.
but if you need a timer with resolution less than 1 ms then I guess it'd be no worse than the regular timers.
Yes, there are problems with rdtsc and multi core CPU's. You have to ensure that the execution don't switches between the cores.

And if i understand it right, it counts on every CPU cycle. Whats about CPU's with variable frequenzy. It's standard on every laptop. And my Core i7 desktop is lowering the frequenzy too, if CPU load is low.

I don't trust this timer.

Posted: Sun Aug 16, 2009 1:50 am
by SFSxOI
works OK with my dual core. Why is it necessary to set the thread priority?

Posted: Sun Aug 16, 2009 3:02 am
by idle
Yes you are right, it cant be trusted

Just what I saw done!
setting it to real time to get an accurate frequency
and by locking it to a CPU with SetThreadAffinityMask, though not sue if that's the same for mulitcore systems.

Don't know how your supposed to deal with the speed step stuff. I guess you could poll the frequency in a thread and fudge it. It's not like the cpu is going to step up and down at random, not when it's being pushed to grunt and if you need high performance time, I expect your intending to make it grunt!

Anyway I give up, was just a thought but after ogooling I realize that it's a problem fraught with difficulty.

Posted: Sun Aug 16, 2009 4:01 am
by idle
maybe this is on track?

initializes the timer getting the frequency and start time
creates a thread to update the frequency while timing is going on
in the call to ElapsedNanoSeconds it will loop if it's got a negative time delta (I don't know if setting the thread affinity mask will keep it on one core or work with hyper threading.

I don't expect it'll be accurate.

see code in first post
2000 MHz
1011 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1014 NanoSeconds 1 MilliSeconds
1017 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1010 NanoSeconds 1 MilliSeconds
1008 NanoSeconds 1 MilliSeconds
1684 NanoSeconds 2 MilliSeconds
1566 NanoSeconds 2 MilliSeconds
1064 NanoSeconds 1 MilliSeconds
1051 NanoSeconds 1 MilliSeconds
1040 NanoSeconds 1 MilliSeconds
1047 NanoSeconds 1 MilliSeconds
1008 NanoSeconds 1 MilliSeconds
1008 NanoSeconds 1 MilliSeconds
1009 NanoSeconds 1 MilliSeconds
1008 NanoSeconds 1 MilliSeconds
1008 NanoSeconds 1 MilliSeconds
Averages 1031 NanoSeconds 1 Milliseconds
2000 MHz

Posted: Mon Aug 17, 2009 4:38 am
by Rescator
m, you do realize that QueryPerformanceCounter may use RDTSC as well?, and in cases when RDTSC can't be trusted it uses the high precision timer of the bios or similar. (there are still some small issues with time skipping with QPC but not as bad as the issues RDTSC has)

So I don't really understand why your not basing this on the QPC instead if you need less than ms timing.

PS! Since you are already using timeBeginPeriod_() you might as well use timeGetTime_() instead of ticks.

Posted: Mon Aug 17, 2009 5:29 am
by idle
There's no reason, other than seeing if it can be done.

I realize that QueryPerfomanceCounter will utilize another source if it can and still be subject to suffer the same problems, which presents a bit of a challenge.

So while I'm aware that it's probably not going to pan out as a solid timer there's no reason for not trying.

locking the thread to a cpu / core should mitigate the issue of drift and periodically measuring the frequency will at least enable it to recover on a speed stepping processor.

But I can't test it anymore, as I don't have a multicore or speed stepping processor.

Posted: Mon Aug 17, 2009 6:09 am
by Rescator
Yeah! It's baffling M$ hasn't a solid solution for this.
Kinda why I love timeGetTime_() (with timeBeginPeriod_() obviously) so much, sure it only gives like 1ms but it's darn reliable, and luckily Im' yet to need nanosecs and I hope I never do ;)

Posted: Mon Aug 17, 2009 6:51 am
by idle
Yes there's not much need for nanosecond timing, if your profiling code your more concerned about how many cycles it takes and if your timing a routine then your going to do it over 100's of iterations.

Still I'll like to see a some result of it on multi core systems.

Posted: Mon Aug 17, 2009 11:06 am
by Thorium
idle wrote:Yes there's not much need for nanosecond timing, if your profiling code your more concerned about how many cycles it takes and if your timing a routine then your going to do it over 100's of iterations.

Still I'll like to see a some result of it on multi core systems.
Well your new source seems to be pretty good.

CPU: Intel Core i7 920 @2,66GHz (4 Cores)

Code: Select all

1055 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1067 NanoSeconds 1 MilliSeconds
1068 NanoSeconds 1 MilliSeconds
1074 NanoSeconds 1 MilliSeconds
1060 NanoSeconds 1 MilliSeconds
1059 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1069 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1067 NanoSeconds 1 MilliSeconds
1054 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1061 NanoSeconds 1 MilliSeconds
1054 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1124 NanoSeconds 2 MilliSeconds
1059 NanoSeconds 1 MilliSeconds
1079 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1054 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1059 NanoSeconds 1 MilliSeconds
1128 NanoSeconds 2 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1053 NanoSeconds 1 MilliSeconds
1090 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1070 NanoSeconds 1 MilliSeconds
1042 NanoSeconds 1 MilliSeconds
1066 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1124 NanoSeconds 2 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1067 NanoSeconds 1 MilliSeconds
1063 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1065 NanoSeconds 1 MilliSeconds
1068 NanoSeconds 1 MilliSeconds
1067 NanoSeconds 1 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1066 NanoSeconds 1 MilliSeconds
1069 NanoSeconds 1 MilliSeconds
1066 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1111 NanoSeconds 2 MilliSeconds
1062 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1119 NanoSeconds 2 MilliSeconds
1057 NanoSeconds 1 MilliSeconds
1059 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1059 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1069 NanoSeconds 1 MilliSeconds
1068 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1103 NanoSeconds 1 MilliSeconds
1055 NanoSeconds 1 MilliSeconds
1058 NanoSeconds 1 MilliSeconds
1063 NanoSeconds 1 MilliSeconds
1073 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
1067 NanoSeconds 1 MilliSeconds
1056 NanoSeconds 1 MilliSeconds
Averages 1064 NanoSeconds 1 Milliseconds
2665 MHz

Posted: Mon Aug 17, 2009 11:38 am
by idle
that looks promising, though not sure how to deal with the issues of the speed changes.