MacroTimersQPC.inc

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

MacroTimersQPC.inc

Post by deltarho[1859] »

Below is "MacroTimersQPC.inc" for Windows.

The Performance Counter has come a long way since it's introduction. Earlier incarnations were only guaranteed to be invariant during a Windows session. There was no synchronisation between cores giving rise to issues during context switching. With modern Windows systems these 'problems' are no more.

There are 16 timers from 0 to 15 and the principle use is for timing sections of code anywhere in our applications. We may have a routine called many times and need a section of it to be a tad faster. We may rewrite the code and hope that it will be faster. By timing the section in question we will know whether we have an improvement or not. One of the great things about peer support forums is that we can post some code and ask "Can anyone get more speed out of this?". We may, of course, have to put our ASM hat on if BASIC is not up to what we want.

The simplest method in the include file is a single pass timer used like this:

StartTimer(0)
' Code to be timed'
StopTimer(0)

We get a simple result from:

Print sTimeTaken(0, 3, False)

The first parameter is the timer index used for the timing.

The second parameter sets the number of decimal places to output:
0 to 3 for 0 to 3 decimal places in milliseconds - ms
4 to 7 for 0 to 3 decimal places in microseconds - us
If the second parameter is negative then we get the full non-formated output in milliseconds.

The third parameter is for choosing between a simple output (False) or a verbose output (True).

For example, a simple output will look like this: 501.246ms

A verbose output will look like this: [1] in MYROUTINE Single: 20.6ms

This tells us that timer 1 in the routine MyRoutine reports 20.6ms for a single pass.

Using sTimetaken() may be delayed until just prior to an applications closure. On executing sTimeTaken, and all the other print routines, the variables used are reset, releasing the index used for further use. Using an index which has not been released will give unpredictable results.

The next type of method is for multiple passes of the section in question.

There is a variety of analyses: Fastest time, Slowest time, Total time, Fast/Slow time and Average time.

Here is an example of Total time:

StartTimer(0)
' Code to time
StopTimer_UpdateTotalTime(0)

and we get a simle result from:

Print sTotalTimeTaken(0,3,False)

A simple output will look like the example above.

A verbose output will look liike this: [0] in RANDOMTIMES Total(10): 83.96ms

Just after Total is the number of times the code in question was timed. More often than not, in my case, I know how many passes there will be. However, we may have a section of code in a routine where we do not know how many times it will be passed.

The StopTimer* macros have a corresponding print routine:

sTimeTaken -> StopTimer
sTotalTimeTaken -> StopTimer_UpdateTotalTime
sFastestTimeTaken -> StopTimer_UpdateFastestTime
sSlowestTimeTaken -> StopTimer_UpdateSlowestTime
sFastSlowTimeTaken -> StopTimer_UpdateFastSlowTime
sAverageTimeTaken -> StopTimer_UpdateTotalTime

The average time print routine needs StopTimer_UpdateTotalTime since the average is, of course, simply the total time divided by the number of passes.

The last statement in StartTimer() is a QueryPerformanceCounter and the first statement of all the StopTimer* macros is a QueryPerformanceCounter. This ensures the best possible timing. The statements before and after these QueryPerformanceCounters obviously take time but it is small and does not appear to have any adverse effects on the surrounding code. However, they will play an adverse role on timings if the timers are nested or interleaved.

There are some miscellaneous macros.

sPerformanceCounterFrequency returns precisely that. When I upgraded from Windows 7 to Windows 10 my default frequency was 3,417,991 or thereabouts. However, enabling HPET in the BIOS and using 'bcdedit /set useplatformclock true' at the Command prompt/PowerShell prompt gets me 14,318,180. I used the HPET with Windows 7 for years without any apparent issues although some gamers prefer not to use it. With the lower frequency it was too slow to detect an API overhead but with the higher frequency it can, although, on my machine, it is only 10 ticks. 10 ticks is only 0.7 microseconds so I don't bother to compensate for that in the macros.

StartHiResClock/StopHiResClock will increase and restore the system clock's default 64Hz to 1000Hz and vice versa respectively giving Timer and Sleep(n,1) a resolution of one millisecond. I normally don't use StopHiResClock in applications in the knowledge that Windows will restore the default at the termination of an application session.

MacroTimersQPC.inc will add about 51KB to an application but, of course, it should not be left in production code.

I have also included a TimerUsage.bas with some examples.

The Timers may be used with either the 32-bit or 64-bit compilers.

Have fun.

David Roberts

MacroTimersQPC.inc

Code: Select all

#Include Once "windows.bi"
#Include Once "string.bi"
#Include Once "win\mmsystem.bi"

Dim Shared As Large_Integer liFreq
Dim Shared As Large_Integer liStart(0 To 15), liStop(0 To 15)
Dim Shared As Large_Integer liTotalTime( 0 To 15 )
Dim Shared As Large_Integer liFastestTime( 0 To 15)
Dim Shared As Large_Integer liSlowestTime( 0 To 15)
Dim Shared As Large_Integer liTimerCallCount( 0 To 15 )
Dim Shared As String sFuncName( 0 To 15 )

QueryPerformanceFrequency @liFreq

#Define sPerformanceCounterFrequency LTrim(Format(liFreq.QuadPart, "###,###,###,###"))

#Macro StartHiResClock ' Timer & Sleep(n,1) will have a 1ms resolution
  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 @liStart(i)
#EndMacro

#Define StopTimer(i) QueryPerformanceCounter @liStop(i)

#Macro StopTimer_UpdateTotalTime(i)
  QueryPerformanceCounter @liStop(i)
  liTotalTime(i).QuadPart += ( liStop(i).QuadPart - liStart(i).QuadPart )
  liTimerCallCount(i).QuadPart += 1
#EndMacro

#Macro StopTimer_UpdateFastestTime(i)
  QueryPerformanceCounter @liStop(i)
  If liTimerCallCount(i).QuadPart = 0 Then
    liFastestTime(i).QuadPart = liStop(i).QuadPart - liStart(i).QuadPart
  Else
    liFastestTime(i).QuadPart = Min( liFastestTime(i).QuadPart, liStop(i).QuadPart - liStart(i).QuadPart )
  End If
  liTimerCallCount(i).QuadPart += 1
#EndMacro

#Macro StopTimer_UpdateSlowestTime(i)
  QueryPerformanceCounter @liStop(i)
  liSlowestTime(i).QuadPart = Max( liSlowestTime(i).QuadPart, liStop(i).QuadPart - liStart(i).QuadPart )
  liTimerCallCount(i).QuadPart += 1
#EndMacro

#Macro StopTimer_UpdateFastSlowTime(i)
  Scope
    Dim As Large_Integer liDummy
    QueryPerformanceCounter @liStop(i)
    liDummy.QuadPart = liStop(i).QuadPart - liStart(i).QuadPart
    If liTimerCallCount(i).QuadPart = 0 Then
      liFastestTime(i).QuadPart = liDummy.QuadPart
      liSlowestTime(i).QuadPart = liDummy.QuadPart
    Else
      liFastestTime(i).QuadPart = Min( liFastestTime(i).QuadPart, liDummy.QuadPart )
      liSlowestTime(i).QuadPart = Max( liSlowestTime(i).QuadPart, liDummy.QuadPart )
    End If
  End Scope
  liTimerCallCount(i).QuadPart += 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 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 ULongLong, As String, As String, As ULongLong, As Long, flag As Long ) As String

' ~~~~~~~~~~

Public 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).QuadPart - liStart(i).QuadPart) * _
      IIf(k<4, 1000, 1000000)/liFreq.QuadPart, s) + IIf(k<4, "ms", "us")
  Else
    s = str( (liStop(i).QuadPart - liStart(i).QuadPart) * 1000/liFreq.QuadPart ) + "ms"
  EndIf
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " Single:" + s
  EndIf
  liStart(i).QuadPart = 0
  Return s

End Function

' ~~~~~~~~~~

Public Function sFastestTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String

  s = FormatOutPut( i, liFastestTime(i).QuadPart, " Fastest(", sFuncName(i), liTimerCallCount(i).QuadPart, j, flag )
  liStart(i).QuadPart = 0
  liTimerCallCount(i).QuadPart = 0
  liFastestTime(i).QuadPart = 0
  Return s
  
End Function

' ~~~~~~~~~~
 
Public Function sSlowestTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String
  
  s = FormatOutput( i, liSlowestTime(i).QuadPart, " Slowest(", sFuncName(i), liTimerCallCount(i).QuadPart, j, flag )
  liStart(i).QuadPart = 0
  liTimerCallCount(i).QuadPart = 0
  liSlowestTime(i).QuadPart = 0
  Return s
  
End Function

' ~~~~~~~~~~
 
Public 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).QuadPart * IIf(k<4, 1000, 1000000)/liFreq.QuadPart, s ) + "~" + _
      Format( liSlowestTime(i).QuadPart* IIf(k<4, 1000, 1000000)/liFreq.QuadPart, s) + IIf(k<4, "ms", "µs")
  Else
    s = Str( liFastestTime(i).QuadPart*1000/liFreq.QuadPart ) + "~" + LTrim(Str( liSlowestTime(i).QuadPart*1000/liFreq.QuadPart )) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " FastSlow(" + LTrim(Str(liTimerCallCount(i).QuadPart)) + "):" + s
  End If
  liStart(i).QuadPart = 0
  liTimerCallCount(i).QuadPart = 0
  liFastestTime(i).QuadPart = 0
  liSlowestTime(i).QuadPart = 0
  Return s
  
End Function

' ~~~~~~~~~~

Public Function sTotalTimeTaken( i As Long, j As Long, flag As Long ) As String
Dim s As String

  s = FormatOutput( i, liTotalTime(i).QuadPart, " Total(", sFuncName(i), liTimerCallCount(i).QuadPart, j, flag )
  liStart(i).Quadpart = 0
  liTotalTime(i).QuadPart = 0
  liTimerCallCount(i).QuadPart = 0
  Return s
  
End Function

' ~~~~~~~~~~

Public 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).QuadPart * IIf(k<4, 1000, 1000000)/(liFreq.QuadPart * liTimerCallCount(i).QuadPart), s) + IIf(k<4, "ms", "us")
  Else
    s = Str(liTotalTime(i).QuadPart*1000/(liFreq.QuadPart * liTimerCallCount(i).QuadPart)) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(i)) + "] in " + sFuncName(i) + " Average(" + LTrim(Str(liTimerCallCount(i).QuadPart)) + "):" + s
  End If
  liStart(i).QuadPart = 0
  liTotalTime(i).QuadPart = 0
  liTimerCallCount(i).QuadPart = 0
  Return s
  
End Function

' ~~~~~~~~~~

Public Function FormatOutput( ourTimer As Long, Scheme as ULongLong, sScheme As String, _
  FuncName As String, Counter as ULonglong , 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.QuadPart, s) + IIf(k<4, "ms", "us")
  Else
    s = Str(Scheme*1000/liFreq.QuadPart) + "ms"
  End If
  If flag = True Then
    s = "[" + LTrim(Str(ourTimer)) + "] in " + FuncName + sScheme + LTrim(Str(Counter)) + "):" + s
  End If
  Return s
 
End Function

' ~~~~~~~~~~
TimersUsage.bas

Code: Select all

#Include Once "MacroTimersQPC.inc"

Declare Sub MyRoutine()
Declare Sub RandomTimes()

Randomize

Print sPerformanceCounterFrequency

StartTImer(0)
  Sleep (500,1)
StopTimer(0)
Print sTimeTaken(0, 3, False)

StartTImer(0)
  Sleep (100,1)
StopTimer(0)
Print sTimeTaken(0, -1, False) ' No formatting

MyRoutine
Print sTimeTaken(0, 2, True)
Print sTimeTaken(1, 2, True)

Dim i as Long
For i = 1 to 10
  RandomTimes
Next
Print sTotalTimeTaken(0, 5, True)
'Print sFastestTimeTaken(0, 5, True)
'Print sSlowestTimeTaken(0, 5, True)
'Print sFastSlowTimeTaken(0, 5, True)
'Print sAverageTimeTaken(0, 3, True)

' ~~~~~~~~~~

Sub MyRoutine()
  
  StartHiResClock
  
  StartTimer(0)
    Sleep (20,1)
  StopTimer(0)
  
  StartTimer(1)
    Sleep (5,1)
  StopTimer(1)
  
  StopHiResClock
  
End Sub

' ~~~~~~~~~~

Sub RandomTimes()
Dim i As Long
Dim x As Single

  StartTimer(0)
    For i = 1 to Rnd*50000
      x = Sin(0.3)
    Next
  StopTimer_UpdateTotalTime(0)
'  StopTimer_UpdateFastestTime(0)
'  StopTimer_UpdateSlowestTime(0)
'  StopTimer_UpdateFastSlowTime(0)
'  StopTimer_UpdateTotalTime(0)
  
End Sub

' ~~~~~~~~~~

Sleep
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

In the above I use

Dim Shared As Large_Integer liFreq

QueryPerformanceFrequency @liFreq

liFreq.QuadPart

and with the other Shared variables.

I did this as this is what I found searching the forums, being new to FreeBASIC.

However, I have been experimenting with ULongLong and, so far, this is working.

MacroTimersQPCII.inc uses ULongUlong resulting in

Dim Shared As ULongLong liFreq

QueryPerformanceFrequency Cast( Large_Integer Ptr, @liFreq )

liFreq

.QuadPart was used 81 times in MacroTimersQPC.inc <smile>

For the time being I am calling the revision MacroTimersQPCII.inc and will try and break it.

MacroTimersQPCII.inc

Code: Select all

#Include Once "windows.bi"
#Include Once "string.bi"
#Include Once "win\mmsystem.bi"

Dim Shared As ULongLong liFreq
Dim Shared As ULongLong liStart(0 To 15), liStop(0 To 15)
Dim Shared As ULongLong liTotalTime( 0 To 15 )
Dim Shared As ULongLong liFastestTime( 0 To 15)
Dim Shared As ULongLong liSlowestTime( 0 To 15)
Dim Shared As ULongLong 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 ' Timer & Sleep(n,1) will have a 1ms resolution
  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 ULonglong 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 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 ULongLong, As String, As String, As ULongLong, As Long, flag As Long ) As String

' ~~~~~~~~~~

Public 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

' ~~~~~~~~~~

Public 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

' ~~~~~~~~~~
 
Public 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

' ~~~~~~~~~~
 
Public 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

' ~~~~~~~~~~

Public 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

' ~~~~~~~~~~

Public 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

' ~~~~~~~~~~

Public Function FormatOutput( ourTimer As Long, Scheme as ULongLong, sScheme As String, _
  FuncName As String, Counter as ULongLong , 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

' ~~~~~~~~~~
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

Found a bug in '#Macro StopTimer_UpdateFastSlowTime(i)' - I had ULongULong instead of ULongLong.

I also had µ instead of u in the sFastSlowTimeTaken routine.

Code in post #2 corrected.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

OK, its all very well printing timings to a console window but what about GUI applications?

Well, what I often do is to wait until just before the application terminates then display a message box with all the timings done.

We could have:

msg = sTimeTaken(0,3,True) + Chr(13,10)

and sometime later

msg += sTimeTaken(1,3,True)

and then before closing

put msg into a message box.

msg will be, obviously, Shared so we can concatenate anywhere in our application.

Being advised of timings on the fly is a different matter. Chances are that the last thing we want is a message box popping up in a callback routine.

For this scenario I use zTrace by Patrice Terrier ( FreeBASIC member ). This little utility uses a window in a separate thread of execution and in my experience has no effect on the running application's performance.

If we compile and run Test3.bas we get this.

Image

Well, not on the first run. By clicking on the client area of the zTrace window a context menu provides us with several options; one of which is setting a display location.

You can get a copy of zTrace at José Roca's ( FreeBASIC member ) website: http://www.jose.it-berater.org/smfforum ... pic=3092.0. You will have to register so as to download. I have been a member for a long time so I am not sure if the registration is as easy as it was. Any problems, get back to me and I will sort it for you.

Test3.bas

Code: Select all

#Include Once "MacroTimersQPCII.inc"

Dim zParam as zstring * 64
Dim As Any Ptr library = DyLibLoad( "zTrace.dll" )
If library = False Then
  Messagebox( Null, "Failed to load zTrace.dll, aborting program...", "MyApplication", MB_OK )
  End 1
End If
Dim zTrace As Function( byval as zString ptr ) As long
zTrace = DyLibSymbol( library, "zTrace" )
If zTrace = False Then
  Messagebox( Null, "Could not retrieve the zTrace function's address from zTrace.dll library, aborting program...", "MyApplication", MB_OK )
  End 1
End If

StartHiResClock
StartTimer(0)
  Sleep (1000,1)
StopTimer(0)
zParam = sTimetaken(0,3,0) ' Convert sTimeTaken() to a null terminated string
zTrace( zParam )

MessageBox( Null, "That is me done <smile>", "MyApplication", MB_OK + MB_TOPMOST)
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

In the thread http://www.freebasic.net/forum/viewtopi ... =3&t=25237 Josep Roca mentioned using Private in inc files.

My memory did a 180 and I used Public in MacroTimersQPC - I should have used Private. Doh!

Dead code removal of unused routines does work.

I compiled TimerUsage.bas as posted and the exe was 62,464 bytes (32-bit). I then commented out the Total test - the Print and macro - and got an exe of 61,952. I then added the FastSlow test and got an exe of 64,000 bytes.

Imagine an inc file with routines up to the rafters and we only use a couple of them in an application.

So, load up your IDE and replace Public with Private.

Apologies - I am a little absent minded at times. Not an age thing - always been that way. Honest <smile>

PS I'm not being very scientific.

I repeated the above using Public instead of Private and got an exe of 66,048 bytes for all three tests; ie no removal.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: MacroTimersQPC.inc

Post by MrSwiss »

Hi,

you are for sure, using some strange data types here:
Large_Integer (looks like a custom Type)
ULongLong (looks like C/C++)
etc.
Fixed size Integer Types in FB are:
  • Byte/UByte (8)
    Short/UShort (16)
    Long/ULong (32)
    LongInt/ULongInt (64)
Integer/UInteger is a different beast:
  • using FBC x32 --> 32bit
    using FBC x64 --> 64bit
Called a dynamic Type ... (aka: Pointer Size of Compiler used).

BTW: include files in FB are using .bi extension (not .inc), stands for: BASIC INCLUDE.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

Thanks, MrSwiss, I have just changed ULongLong to ULongInt and none of my roof tiles came off. <smile>
BTW: include files in FB are using .bi extension (not .inc), stands for: BASIC INCLUDE.
The link in my last post was spawned from http://www.freebasic.net/forum/viewtopi ... 30#p226853

On page #1 Paul Squires wrote
I think that is a reasonable way to interpret it. [ Inserted: referring a comment by Imortis ] Jose and myself come from a mostly PowerBasic background and *.inc is just a simple way to add chunks of code to a main *.bas file without having to create static libs or DLLs. Simply assemble code into manageable, somewhat reasonably self contained *.inc files and #INCLUDE them into the man .bas file. With the speed of the compiler on modern hardware the compile is really fast even for applications that have many #INCLUDES.

Personally, I use *.bi files as one would use *.h files in C. Mostly for declarations. I tend to think of *.bas files as ones that should be compiled either into an EXE, LIB or DLL.

It's all just a matter of preference and what you've become accustomed to over many years. It might not be what has been used traditionally in FB but it does make sense. No intentions here to try to convert anyone's way of thinking.

I assume that you saw that as you posted four posts before that, although you did not post again in that thread.

I can understand your writing "As you can clearly see from Imortis's Post: you are just causing unnecessary confusion (that didn't exist before!)." but, personally, I don't find it confusing. I also came here from PowerBASIC but have used many BASICs since 1973 when BASIC was just nine years old prior to which I wrote with either Fortran or Algol.

I have only just started with FreeBASIC but have spent a lot of my time going through bi files and they tend to be full of definitions, declarations, user-defined types and so on and although they are intended to be included with a bas file I don't see them in the sense as source code but as Paul does with his comparison with *.h files in C.

On the other hand I see an inc file as Imortis did when reading Josep Roca's comments. Imortis wrote [ Inserted: See the inserted comment above ] "So is the .inc file only to indicate that the file has no code that will directly execute should you compile it?" to which José wrote "Yes, you're correct."

You wrote ' Nobody on FB could care less about "what others do". ' That made me chuckle because I am the very opposite and it seems to me that Immortis and I are on the same page in that respect.

So, when writing FreeBASIC code I will tend to think bas, bi and inc.

Sorry for the lengthy response but I tend to be of the school of thought that asks "Why use a word when a sentence will do".

I apologise to admin because 'Tips and Tricks' is for source code only; although the opening sentence qualifies.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: MacroTimersQPC.inc

Post by MrSwiss »

Hi deltarho[1859],

I've also had a short stint with PB. Just as it had switched back to Rob Zale (from Borland).
In those days I've included my own ASM routines (TASM) into PB, which the Linker mostly
took, without complaining too much <wide grin>.

I still don't like the .inc stuff on FB, simply because the IDE I'm using (FBEdit) is either not
*highlighting* correctly, or disallows me, to open an .inc from within (after recognizing it).

None the less, I understand the wish, to better distinguish but, you can't have it all ...
(the cookie and eat it)
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

I still don't like the .inc stuff on FB, simply because the IDE I'm using (FBEdit) is either not
*highlighting* correctly, or disallows me, to open an .inc from within (after recognizing it).
Ah, I have FBEdit on board but I haven't taken to it. I am using WinFBE and it is treating my inc files as if they bas files.

Anyway, we should not be discussing IDEs in this forum, otherwise admin will be having words with us.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: MacroTimersQPC.inc

Post by MichaelW »

You could improve the repeatability, cutting the uncertainty in the timed period in half, by synchronizing with the timer counter before you start the timed period. In case what I mean by synchronizing is not clear, you implement a loop that delays until the counter value changes.

You can get an idea of the repeatability by timing against the core TSC, which on recent systems has a much higher frequency than the performance counter, but on a multi-core system you must restrict your process to a single core, because the core TSCs are likely to be out of sync. See SetProcessAffinityMask. You will need to do this part in assembly, using the RDTSC instruction to read the value of the TSC twice, once before your timed period, and once after, and the difference is the timed period in processor clock cycles. There is code here that could reasonably be adapted. There is also a 64-bit version somewhere here, that I did not have time to find.
deltarho[1859]
Posts: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: MacroTimersQPC.inc

Post by deltarho[1859] »

I used to force single core usage when using the performance counter for timing in the old days but there is no need nowadays.

From Microsoft: "We strongly discourage using the RDTSC or RDTSCP processor instruction ....... Instead, we encourage you to use QPC to leverage the abstraction, consistency, and porability that it offers." See Acquiring high-resolution time stamps

Admittedly the TSC has a much higher resolution than QPC but since my timings will be in the milli-second/micro-second domain then I do not need it. A frequency of 14,318,180Hz is more than adequate for my purposes.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: MacroTimersQPC.inc

Post by MrSwiss »

Checked MSDN on details about QPC. The used MS-Struct "LARGE_INTEGER" is actually a LongInt (signed 64bit Integer).

It is similar to:

Code: Select all

Union LARGE_INTEGER
    As LongInt Int64    ' signed qword
    Type
        As ULong low32  ' unsigned dword
        As Long  high32 ' signed dword
    End Type
End Union
But it clearly states: your Compiler might contain a native 64bit equivalent. So, the Union is just a showcase.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: MacroTimersQPC.inc

Post by Josep Roca »

> Thanks, MrSwiss, I have just changed ULongLong to ULongInt and none of my roof tiles came off. <smile>

ULONGLONG is defined in the FB headers as ULONGINT (type ULONGLONG as ulongint), so it doesn't matter which of the two names do you use.
MrSwiss
Posts: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: MacroTimersQPC.inc

Post by MrSwiss »

Josep Roca wrote:ULONGLONG is defined in the FB headers as ULONGINT (type ULONGLONG as ulongint), so it doesn't matter which of the two names do you use.
I don't agree, since I don't want to have to include any FB headers (Windows.bi and the like), when
I'm doing console only stuff. This also applies, to fbGFX only stuff. Timing should be possible, with
any sort of program (without clutter). Just straight referencing the needed WIN-DLL.
Josep Roca
Posts: 564
Joined: Sep 27, 2016 18:20
Location: Valencia, Spain

Re: MacroTimersQPC.inc

Post by Josep Roca »

But the posted code already includes some headers, otherwise it could not call Windows API functions such QueryPerformanceCounter. Therefore, in this case it does not matter. It you don't want to include any header and call the API functions dynamically, it's your business.
Post Reply