Timing using clock cycles

Windows specific questions.
Post Reply
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Timing using clock cycles

Post by deltarho[1859] »

I should tell you that Microsoft discourages using the time stamp counter for high-resolution timing for a variety of reasons. However, I have found the two procedures, GetTSCBegin and GetTSCEnd, to be reliable if two aspects are true.

The first one is your CPU has the rdtscp instruction. I won't bore you with what that does, but it is a variant of rdtsc.

The second one is your system has an invariant time stamp counter. With this, the time stamp counter is synchronized for all cores.

If you first run the following, you will be advised if the two criteria above are true or not.

Code: Select all

#include once "windows.bi"
 
Function RDTSCP_OK As Long
Dim ulReturn As ULong
Asm
  mov eax, &H080000001
  cpuid
  bt edx, &H01B
  jnc 0f
  mov Dword Ptr[ulReturn], 1
0:
End Asm
Return ulReturn
End Function
 
Function InvariantTSC As Long
Dim ulReturn As Ulong
ASM
  mov eax, &H080000007
  cpuid
  bt edx, &H008
  jnc 0f
  mov Dword Ptr[ulReturn], 1
0:
End Asm
Return ulReturn
End Function
 
If RDTSCP_OK = 1 Then
  messagebox( null, "You have rdtscp.", "RDTSCP?", mb_ok )
Else
  messagebox( null, "You don't have rdtscp.", "RDTSCP?", mb_ok )
End If
If InvariantTSC = 1 Then
  messagebox( null, "You have an invariant time stamp counter.", "InvariantTSC?", mb_ok )
Else
  messagebox( null, "You don't have an invariant time stamp counter.", "InvariantTSC?", mb_ok )
End If
 
Sleep
If you have got this far and the above are true, here are the two timing functions.

Code: Select all

Function GetTSCBegin() As UlongInt
Dim As Ulongint x
Asm
  cpuid
  rdtsc
  mov dword ptr[x[0]], eax
  mov dword ptr[x[4]], edx
End Asm
Return x
End Function
 
Function GetTSCEnd() As UlongInt
Dim As Ulongint x
Asm
  rdtscp
  mov dword ptr[x[0]], eax
  mov dword ptr[x[4]], edx
  cpuid
End Asm
Return x
End Function
Here is a test:

Code: Select all

#include once "windows.bi"
Dim As UlongInt Freq, TimeNow, Target, timebegin, timeend
 
QueryPerformanceFrequency Cast( Large_Integer Ptr, @Freq)
QueryPerformanceCounter Cast( Large_Integer Ptr, @TimeNow )
 
Target = TimeNow + Freq/1000    ' TimeNow + 1ms
timebegin = GetTSCBegin
Do
  QueryPerformanceCounter Cast( Large_Integer Ptr, @TimeNow )
Loop Until TimeNow >= Target
timeend = GetTSCEnd
Print timeend - timebegin

Sleep 
On my machine I get 3499303. With a nominal frequency of 3.5GHz, excluding turbo, then the true value is 3500000 for one millisecond. That is nearly four significant figures accurate. Of course, we will probably be timing code a lot less than one millisecond. If the code to time exceeds five microseconds, then the Performance Counter should be used; assuming a Performance Counter frequency of at least 10MHz. Use the above then for very short periods.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timing using clock cycles

Post by deltarho[1859] »

We can convert clock cycles to time.

Example:

Code: Select all

Dim As UlongInt timebegin, timeend
timebegin = GetTSCBegin
For i as Ulong = 1 to 10^8
  Rnd
Next
timeend = GetTSCEnd
print (timeend - timebegin)/(3.5*10^9);" seconds" ' 3.5GHz nominal CPU frequency
Print 3.5*10^11/(timeend - timebegin);" MHz"
 
Dim As Double t
t = Timer
For i As Ulong = 1 to 10^8
  Rnd
Next
t = Timer - t
print t
print 100/t;" MHz"
 
Sleep

I get:

1.216436528 seconds
82.20733075519745 MHz
1.215992900004494 seconds
82.237322273535 MHz

Since t, on my machine, is 1.2 seconds, we shouldn't be using the clock cycles timing, but at least it shows they don't go haywire for longish periods.

Added:

The above code and the test in the opening post work with gas and gcc 32/64, but not gas64. gas64 dislikes my timing functions.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timing using clock cycles

Post by deltarho[1859] »

When it comes to short periods, I would suggest your test piece be put in a loop of at least 10^3 passes.

I timed PCG32II and got the following:

Code: Select all

loop  time   MHz
10^8 0.328s  305
10^3 3.265ms 306
10^2 0.355ms 281
As you can see with a loop of 10^2 we are drifting because the loop is too small.

So, make the loop take at least 5ms and you should be OK.

You can time the loop with (timeend - timebegin)/(3.5*10^9) whatever your CPU nominal frequency is. To get the time of an individual test piece, then, obviously, simply divide the loop time with the loop counter.

Of course, the MHz calculation may be of no interest to you if your test piece has nothing to do with random numbers; which it probably hasn't. :)
SARG
Posts: 1756
Joined: May 27, 2005 7:15
Location: FRANCE

Re: Timing using clock cycles

Post by SARG »

deltarho[1859] wrote: Mar 16, 2023 10:58 The above code and the test in the opening post work with gas and gcc 32/64, but not gas64. gas64 dislikes my timing functions.
Thanks for the report.
Not very hard to fix :D : https://users.freebasic-portal.de/sarg/ ... astest.zip
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timing using clock cycles

Post by deltarho[1859] »

@SARG

You are a nuisance – I've got six toolchains!

Well done. :wink:
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timing using clock cycles

Post by deltarho[1859] »

@SARG

Good job I didn't empty my recycle bin. You sent an update to fbc 1.10.0

So, I put back fbc 1.09.0.

I have two toolchains with 1.10.0 so put your update into them.

Oh boy, that was close. :)

fbc 1.10.0 should be with us in the near future.
Post Reply