timeBeginPeriod

Windows specific questions.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: timeBeginPeriod

Post by dodicat »

I note that "timeBeginPeriod" and "timeEndPeriod" are only useable when gfx is called.

Code: Select all



Declare Function settimer       Alias "timeBeginPeriod"(As Ulong=1) As Long
Declare Function freetimer      Alias "timeEndPeriod"  (As Ulong=1) As Long

screen 17  '<------  comment out
 sleep 500
 dim as double t1,t2
 t1= timer
 settimer
 sleep 1
 freetimer
 t2=timer
 print (t2-t1)*1000
  
 sleep 
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

Code: Select all

'#console on
#Include Once "windows.bi"
#Include Once "win\mmsystem.bi"

Dim As MMRESULT SetRes, RevokeRes
'Screen 17  '<------  comment out
Sleep 500
Dim As Double t1, t2
t1 = Timer
SetRes = timeBeginPeriod(1)
Sleep 1, 1 '  See note below
RevokeRes = timeEndPeriod(1)
t2 = Timer
Print ( t2 - t1 )*1000
  
Sleep 
I am using Sleep's keyflag = 1 for best results.

We can expect a time between 1ms & 2ms since timer resolution is at 1ms.

I am not being pedantic, but when I use the Windows APIs I stick to the MSDN data types like glue.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: timeBeginPeriod

Post by dodicat »

I can use the console by using
screeninfo or screenptr
The graphics libs and winmm are still called.

Code: Select all

#cmdline "-v"
Declare Function settimer       Alias "timeBeginPeriod"(As Ulong=1) As Long
Declare Function freetimer      Alias "timeEndPeriod"  (As Ulong=1) As Long

if screenptr=0 then
 sleep 500
 dim as double t1,t2
 t1= timer
 settimer
 sleep 1,1
 freetimer
 t2=timer
 print (t2-t1)*1000
 end if 
 sleep
 
  
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

Still on topic.

Six years ago I wrote MacroTimersQPC.inc and half way through the thread VeryHiResSleep was introduced. I won't explain the method employed as that is covered in the aforementioned link. It had a small issue which did not always manifest itself and was removed on an update,

What we now have is a more robust millisecond sleep compared with the original version. HPET is enabled in the BIOS and the Performance Counter freqency is 14318180Hz.

I did one test requesting a sleep of five minutes (300,000ms) and got 300000.0039809529ms with an effective resolution of 4 microseconds. That isn't bad for a machine which does not have a real-time OS.

Of that 300000ms a little more than 299998ms was covered by Sleep helped by timeBeginPeriod(1) to get us within a neighbourhood of the target delay and less than two milliseconds was covered by the Performance Counter to fine tune Sleep's greater granularity. The less than two milliseconds CPU intense activity should have no bearing on your application.

Here is a typical output of the code following.

Image

Note, the two millisecond figure is at the top end of what we can expect with timeBeginPeriod(1)'s resolution of 1ms. The 1ms figure is very close to being 'bang on' 1ms.

The original code used 'Sleep ( n-1, 1 )'. There was always the possibility that we could slightly overshoot Target; even though in theory it shouldn't have been. With 'Sleep ( n-2, 1 )' that is now impossible.

Here is the MACRO code

Code: Select all

'#console on

#include Once "I:\FreeBASIC\MacroTimers\MacroTimersQPC.inc" ' [1]

' ********************
' VeryHiResSleep Macro
#Include Once "windows.bi"
#Include Once "win\mmsystem.bi"

Dim As MMRESULT SetHiRes, RevokeHiRes
Dim As ULongInt ullFreq
QueryPerformanceFrequency Cast( Large_Integer Ptr, @ullFreq)

#Macro VeryHiResSleep( n ) ' n >= 1
  Dim As ULongInt Target, TimeNow
  QueryPerformanceCounter Cast( Large_Integer Ptr, @TimeNow )
  Target = TimeNow + n*ullFreq/1000
  If n < 3 then ' Performance counter will be used for less than two milliseconds
    Do
      QueryPerformanceCounter Cast( Large_Integer Ptr, @TimeNow ) ' Class A amplifier analogy
    Loop Until TimeNow >= Target
  Else
    SetHiRes = timeBeginPeriod(1)
    Sleep ( n-2, 1 ) ' Class B amplifier analogy
    RevokeHiRes = timeEndPeriod(1)
    Do
     QueryPerformanceCounter Cast( Large_Integer Ptr, @TimeNow ) ' Class A amplifier analogy
    Loop Until TimeNow >= Target
  End If
#EndMacro
' ********************

Print "HPET enabled in BIOS"
Print "Performance counter frequency: ";ullFreq
Print

' Usage example:
For i As ULong = 1 to 20 ' Milliseconds
  StartTimer(0) ' Found in [1]
  VeryHiResSleep( i )
  StopTimer(0) ' Found in [1]
  Print i;Tab(6);nTimeTaken(0) ' Found in [1]
Next

Sleep
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: timeBeginPeriod

Post by srvaldez »

hello deltarho[1859], where is nTimeTaken defined?
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

Oops :)

nTimeTaken is in a version of MacroTimersQPC.inc newer than the given link.

Code: Select all

Private Function nTimeTaken(Byval i As Ubyte) As Double
Dim n As Double
  n = (liStop(i) - liStart(i))*1000/liFreq
  liStart(i) = 0
  Return n
End Function
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: timeBeginPeriod

Post by srvaldez »

perhaps you should update MacroTimersQPC.inc because as it is the function you just provided won't work, liStop and liStart are undefined
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

I have just done a source code comparison between the forum link inc and the inc on my drive as used above. They are different designs, so dropping what I use now onto the forum will break that thread. We have an analogy to dll hell.

I cannot remember when I redesigned the inc, but it looks like it didn't get published (for my use only).

Here is MacroTimersQPC.inc as used above. It does not have the macro above, but does have nTimeTaken.

Code: Select all

#Include Once "windows.bi"
#Include Once "string.bi"
#Include Once "win\mmsystem.bi"
 
Dim Shared As ULongInt liFreq
Dim Shared As ULongInt liStart(0 To 15), liStop(0 To 15)
Dim Shared As ULongInt liTotalTime( 0 To 15 )
Dim Shared As ULongInt liFastestTime( 0 To 15)
Dim Shared As ULongInt liSlowestTime( 0 To 15)
Dim Shared As ULongInt liTimerCallCount( 0 To 15 )
Dim Shared As String sFuncName( 0 To 15 )
 
QueryPerformanceFrequency Cast( Large_Integer Ptr, @liFreq)
 
#Define sPerformanceCounterFrequency LTrim(Format(liFreq, "###,###,###,###"))
 
#Macro StartHiResClock
  Scope
    Dim As TIMECAPS tc
    TimeGetDevCaps( @tc, SizeOf(tc) )
    TimeBeginPeriod(tc.wPeriodMin)
  End Scope
  Sleep (16,1) ' Tests have shown that the new resolution will not 'bite' until next 'old' tick
#EndMacro
 
#Macro StopHiResClock
  Scope
    Dim As TIMECAPS tc
    TimeGetDevCaps( @tc, SizeOf(tc) )
    TimeEndPeriod(tc.wPeriodMin)
  End Scope
  Sleep (2,1) ' Tests have shown that the new resolution will not 'bite' until next 'old' tick
#EndMacro
 
#Macro StartTimer(i)
  sFuncName(i) = __FUNCTION__
  QueryPerformanceCounter Cast( Large_Integer Ptr, @liStart(i) )
#EndMacro
 
#Define StopTimer(i) QueryPerformanceCounter Cast( Large_Integer Ptr, @liStop(i) )
 
#Macro StopTimer_UpdateTotalTime(i)
  QueryPerformanceCounter Cast( Large_Integer Ptr, @liStop(i) )
  liTotalTime(i) += ( liStop(i) - liStart(i) )
  liTimerCallCount(i) += 1
#EndMacro
 
#Macro StopTimer_UpdateFastestTime(i)
  QueryPerformanceCounter Cast( Large_Integer Ptr, @liStop(i) )
  If liTimerCallCount(i) = 0 Then
    liFastestTime(i) = liStop(i) - liStart(i)
  Else
    liFastestTime(i) = Min( liFastestTime(i), liStop(i) - liStart(i) )
  End If
  liTimerCallCount(i) += 1
#EndMacro
 
#Macro StopTimer_UpdateSlowestTime(i)
  QueryPerformanceCounter Cast( Large_Integer Ptr, @liStop(i) )
  liSlowestTime(i) = Max( liSlowestTime(i), liStop(i) - liStart(i) )
  liTimerCallCount(i) += 1
#EndMacro
 
#Macro StopTimer_UpdateFastSlowTime(i)
  Scope
    Dim As ULongInt liDummy
    QueryPerformanceCounter Cast( Large_Integer Ptr, @liStop(i) )
    liDummy = liStop(i) - liStart(i)
    If liTimerCallCount(i) = 0 Then
      liFastestTime(i) = liDummy
      liSlowestTime(i) = liDummy
    Else
      liFastestTime(i) = Min( liFastestTime(i), liDummy )
      liSlowestTime(i) = Max( liSlowestTime(i), liDummy )
    End If
  End Scope
  liTimerCallCount(i) += 1
#EndMacro
 
#Macro SetDecimalPlaces( a )
  Select Case a+4*(a>3)+1
    Case 1
      s = "######"
    Case 2
      s = "######.#"
    Case 3
      s = "######.##"
    Case 4
      s = "######.###"
  End Select
#Endmacro
 
Declare Function nTimeTaken( As Ubyte ) As Double
Declare Function sTimeTaken( As Long, As Long, As Long ) As String
Declare Function sTotalTimeTaken( As Long, As Long, As Long ) As String
Declare Function sFastestTimeTaken( As Long, As Long, As Long ) As String
Declare Function sSlowestTimeTaken( As Long, As Long, As Long ) As String
Declare Function sFastSlowTimeTaken( As Long, As Long, As Long ) As String
Declare Function sAverageTimeTaken( As Long, As Long, As Long ) As String
Declare Function FormatOutput( As Long, As ULongInt, As String, As String, As ULongInt, As Long, flag As Long ) As String
 
' ~~~~~~~~~~
 
Private Function nTimeTaken(Byval i As Ubyte) As Double
  Dim n As Double
  n = (liStop(i) - liStart(i))*1000/liFreq
  liStart(i) = 0
  Return n
End Function
 
Private Function sTimeTaken( i As Long, j As Long, flag As Long) As String
Dim s As String
Dim k As Long
 
  If j>= 0 Then
    k = Min(j,7)
    SetDecimalPlaces( k )
    s = " " + Format( (liStop(i) - liStart(i)) * _
      IIf(k<4, 1000, 1000000)/liFreq, s) + IIf(k<4, "ms", "us")
  Else
    s = str( (liStop(i) - liStart(i)) * 1000/liFreq ) + "ms"
  EndIf
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " Single:" + s
  EndIf
  liStart(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function sFastestTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
 
  s = FormatOutPut( i, liFastestTime(i), " Fastest(", sFuncName(i), liTimerCallCount(i), j, flag )
  liStart(i) = 0
  liTimerCallCount(i) = 0
  liFastestTime(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function sSlowestTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
 
  s = FormatOutput( i, liSlowestTime(i), " Slowest(", sFuncName(i), liTimerCallCount(i), j, flag )
  liStart(i) = 0
  liTimerCallCount(i) = 0
  liSlowestTime(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function sFastSlowTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
Dim k As Long
 
  If j >= 0 Then
    k = Min(j,7)
    SetDecimalPlaces(k)
    s = " " + Format( liFastestTime(i) * IIf(k<4, 1000, 1000000)/liFreq, s ) + "~" + _
      Format( liSlowestTime(i)* IIf(k<4, 1000, 1000000)/liFreq, s) + IIf(k<4, "ms", "us")
  Else
    s = Str( liFastestTime(i)*1000/liFreq ) + "~" + LTrim(Str( liSlowestTime(i)*1000/liFreq )) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " FastSlow(" + LTrim(Str(liTimerCallCount(i))) + "):" + s
  End If
  liStart(i) = 0
  liTimerCallCount(i) = 0
  liFastestTime(i) = 0
  liSlowestTime(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function sTotalTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
 
  s = FormatOutput( i, liTotalTime(i), " Total(", sFuncName(i), liTimerCallCount(i), j, flag )
  liStart(i) = 0
  liTotalTime(i) = 0
  liTimerCallCount(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function sAverageTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
Dim k As Long
 
  If j >= 0 Then
    k = Min(j,7)
    SetDecimalPlaces(k)
    s = " " + Format( liTotalTime(i) * IIf(k<4, 1000, 1000000)/(liFreq * liTimerCallCount(i)), s) + IIf(k<4, "ms", "us")
  Else
    s = Str(liTotalTime(i)*1000/(liFreq * liTimerCallCount(i))) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " Average(" + LTrim(Str(liTimerCallCount(i))) + "):" + s
  End If
  liStart(i) = 0
  liTotalTime(i) = 0
  liTimerCallCount(i) = 0
  Return s
 
End Function
 
' ~~~~~~~~~~
 
Private Function FormatOutput( ourTimer As Long, Scheme as ULongInt, sScheme As String, _
  FuncName As String, Counter as ULongInt , j As Long, flag As Long ) As String
Dim k As Long
Dim s As String
 
  If j >= 0 Then
    k = Min( j, 7 )
    SetDecimalPlaces( k )
    s = " " + Format(Scheme * IIf(k<4, 1000, 1000000)/liFreq, s) + IIf(k<4, "ms", "us")
  Else
    s = Str(Scheme*1000/liFreq) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(ourTimer)) + "] in " + FuncName + sScheme + LTrim(Str(Counter)) + "):" + s
  End If
  Return s
 
End Function
 
' ~~~~~~~~~~
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: timeBeginPeriod

Post by srvaldez »

thanks deltarho[1859]
but your example code of 5 posts up don't work without the macro, after adding the macro your example works but the output is different

Code: Select all

HPET enabled in BIOS
Performance counter frequency: 10000000

1     1
2     2
3     3
4     4
5     5
6     6.0001
7     7.0002
8     8.0001
9     9.000299999999999
10    10.0003
11    11.0002
12    12
13    13
14    14.0001
15    15
16    16.0001
17    17.0001
18    18
19    19.0003
20    20.0001
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

I wish we had post numbers instead of having to write "5 posts up". As I write this, I cannot say that.

If you Copy and Paste post #4 it will work. The macro is a standalone and was not designed to drop into MacroTimersQPC.inc.
The reason for using MacroTimersQPC.inc was so that we could use StartTimer(0)/StopTimer(0) fo timing purposes.

Using the macro in our code does not require MacroTimersQPC.inc.

You are jumping the gun and doing something which I did not say.
srvaldez
Posts: 3379
Joined: Sep 25, 2005 21:54

Re: timeBeginPeriod

Post by srvaldez »

yes, sorry for misunderstanding
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: timeBeginPeriod

Post by dodicat »

From Microsoft
"The GetSystemTimePreciseAsFileTime function retrieves the current system date and time with the highest possible level of precision (<1us). The retrieved information is in Coordinated Universal Time (UTC) format."
So, believing that is maybe true:

Code: Select all


#inclib "winmm"
extern "windows"
Declare Function settimer   Alias "timeBeginPeriod"(As Ulong=1) As Long
Declare Function freetimer  Alias "timeEndPeriod"  (As Ulong=1) As Long
Declare Sub GetSystemTimePreciseAsFileTime Alias "GetSystemTimePreciseAsFileTime"(p As Any Ptr)
end extern
Type _FILETIME
      LowDateTime As Long
      HighDateTime As Long
End Type

Function Ftick64() As Ulongint
      Dim As _FILETIME f=Any
      Dim As Ulongint flow=Any,fhigh=Any,mytime=Any
      GetSystemTimePreciseAsFileTime(@f)
      fLow  = f.LowDateTime
      fHigh = f.HighDateTime
      myTime = fLow Or (fHigh Shl 32)
      Return myTime
End Function


Sleep 50
Dim As String s
dim as ulongint t
For n As Long=1 To 200
       t=Ftick64
      settimer
      Sleep 1
      freetimer
      s+=Str((Ftick64-t)\10)+!" microseconds\n"
Next
Print s
Sleep

 
adeyblue
Posts: 300
Joined: Nov 07, 2019 20:08

Re: timeBeginPeriod

Post by adeyblue »

To lump another into the alternatives, if you want to measure how much work the thread did rather than the entire system for a piece of code then QueryThreadCycleTime (also What does QueryThreadCycleTime measure?)

To illustrate the difference (it's in C, I don't have any access to FB right now)

Code: Select all

void TestEm()
{
	HANDLE curThread = GetCurrentThread();
	ULONG64 threadCycTimeStart, threadCycTimeEnd;
	LARGE_INTEGER perfCounterStart, perfCounterEnd, perfCounterFreq;
	ULONG64 rdtscStart, rdtscEnd;
	
	QueryPerformanceCounter(&perfCounterStart);
	QueryThreadCycleTime(curThread, &threadCycTimeStart);
	rdtscStart = __rdtsc();

	// thing to measure
	Sleep(2500);

	QueryPerformanceCounter(&perfCounterEnd);
	QueryThreadCycleTime(curThread, &threadCycTimeEnd);
	rdtscEnd = __rdtsc();
	
	QueryPerformanceFrequency(&perfCounterFreq);
	printf("After 2.5 seconds\nperfCounterUnits: Start %I64d, dur %I64d, freq = %I64d\nthreadCycTime: Start %I64u, dur %I64u\nrdtsc: Start %I64u, dur %I64u\n",
		perfCounterStart.QuadPart,
		perfCounterEnd.QuadPart - perfCounterStart.QuadPart,
		perfCounterFreq.QuadPart,
		threadCycTimeStart,
		threadCycTimeEnd - threadCycTimeStart,
		rdtscStart,
		rdtscEnd - rdtscStart
	);
}
output wrote: After 2.5 seconds
perfCounterUnits: Start 3918851448070, dur 5845932, freq = 2338466
threadCycTime: Start 27300220, dur 25528
rdtsc: Start 2486590051893991, dur 5986234434
This system is 2.4GHz and you can see rdtsc and QPC counted roughly 6GHz worth of cycles (i.e 2.4 * 2.5 seconds) but the thread cycle time is much, much less than that because it doesn't count up when the thread is switched out and not doing anything. It meaures a different thing to others and it can be useful.
dodicat
Posts: 7983
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: timeBeginPeriod

Post by dodicat »

Quick translate of adeyblue, I started, so I just finished, hope you don't mind.
Note: for 32 bits -gen gcc has to be stipulated, either in code or command line switch.

Code: Select all

#cmdline "-gen gcc"

#include "windows.bi"
#include "crt.bi"
declare function QueryThreadCycleTime alias "QueryThreadCycleTime"(threadhandle as handle,cycletime as PULONG64) as boolean
declare function __rdtsc cdecl alias " __builtin_ia32_rdtsc"() as ulong64
sub TestEm()

	dim as HANDLE curThread = GetCurrentThread()
	dim as ULONG64 threadCycTimeStart, threadCycTimeEnd
	dim as LARGE_INTEGER perfCounterStart, perfCounterEnd, perfCounterFreq
	dim as ULONG64 rdtscStart, rdtscEnd
	
	QueryPerformanceCounter(@perfCounterStart)
	QueryThreadCycleTime(curThread, @threadCycTimeStart)
	rdtscStart = __rdtsc()

	'// thing to measure
	Sleep(2500)

	QueryPerformanceCounter(@perfCounterEnd)
	QueryThreadCycleTime(curThread, @threadCycTimeEnd)
	rdtscEnd = __rdtsc()
	
	QueryPerformanceFrequency(@perfCounterFreq)
	printf(!"After 2.5 seconds\nperfCounterUnits: Start %I64d, dur %I64d, freq = %I64d\nthreadCycTime: Start %I64u, dur %I64u\nrdtsc: Start %I64u, dur %I64u\n", _
		perfCounterStart.QuadPart, _
		perfCounterEnd.QuadPart - perfCounterStart.QuadPart, _
		perfCounterFreq.QuadPart, _
		threadCycTimeStart, _
		threadCycTimeEnd - threadCycTimeStart, _
		rdtscStart, _
		rdtscEnd - rdtscStart)
	
end sub
puts("please wait a couple of seconds")
TestEm()
system_("pause")
 
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: timeBeginPeriod

Post by deltarho[1859] »

GetSystemTimePreciseAsFileTime >= Windows 8
Post Reply