FreeBasic's Timer

General FreeBASIC programming questions.
Post Reply
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

No.
If 't1' is not modified, then at the next loop, 't2 < t1' will still be checked and therefore 't3' will still be decremented by one day and so on !
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

Agreed. :)
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

I reckoned that fxm spent sometime before settling on 'If t > 300' and did not question it. However, I decided to do just that.

With t <= 300 we don't invoke Sleep and the onus of the delay is solely with the loop; which could mean 'looking for' up to 300ms.

With t = 500, for example, we request a Sleep for 200ms (500 - 300). Because of Sleep's resolution, we actually sleep for Int(200/15.625 + 1) * 15.625 = 203.125. The loop now has the job of 'finding' 296.875ms (500 - 203.125).

With t = 1000 the loop, again, has the job of 'finding' 296.875ms.

Polling Timer is CPU intensive, so for t > 300 one of the CPU's cores will be going flat out for nearly 300ms which is not good for the system.

We need to reduce the amount of work done in the loop.

I looked at 16, instead of 300, but this was too close to Sleep's resolution of 15.625ms and some 'rogue' values appeared in the delay. This was easily resolved by using 32.

We now have the loop only having to 'find' no more than 32ms.

Going flat out for 32ms is a lot better than going flat out for 300ms.

We won't be doing this, but is a worst-case scenario.

Code: Select all

For i As Ulong = 1 to 20
  delay(1000)
Next
The first test used 'If t > 300' and the second test used 'If t > 32'.

Here are a couple of snapshots of Task Manager.

Image

As you can see, the system has a definite preference for 32. :)
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

I agree that the 32ms threshold seems like a good compromise between accuracy and CPU load.

In the case of my 2 procedures 'delay()' and 'regulate()':
- 'delay()' for me has rather a use of a one shoot from time to time, but with a good accuracy, so well that I did not spare the CPU load too much with the threshold of 300ms (I could have as well take 100ms).
- 'regulate()' on the contrary is rather adapted to insert a delay in a loop to often adjust its FPS, and there with the threshold of 20ms, I rather wanted to favor a little the limitation of the CPU load compared to the accuracy.

In conclusion, I agree to use the same single threshold of 32ms (trade-off between accuracy and CPU load) for both procedures.
All my previous posts have been updated accordingly.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

With regard delay() there is no compromise between accuracy and CPU load.

With a delay of 1000ms, for example, previously we had, roughly, 700ms Sleep and 300ms loop. With the 32ms threshold we now have, roughly, 968ms Sleep and 32ms loop.

There is a dramatic reduction in CPU load but no change in accuracy.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

You are right, I do not find any noticeable degradation in accuracy between a threshold at 300 ms and the threshold at 32 ms, but on the other hand I observe a clear degradation between the threshold at 32 ms and a threshold at 20 ms.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

fxm wrote:but on the other hand I observe a clear degradation between the threshold at 32 ms and a threshold at 20 ms.
Yes, doubling Sleep's 'out of the box' resolution keeps us out of trouble. Playing it safe, so to speak.

And there is more. :)

With my VeryHiResSleep( n ) macro I had an issue with Sleep's association with the system clock's frequency of 64Hz, so I associated it with a multimedia timer with a frequency of 1000Hz. That helped.

Linked to a 1ms resolution timer, a request for 1ms will give a Sleep of between 1ms and 2ms. Again, playing it safe, I chose 4ms.

We can now use:

Code: Select all

If t > 4 Then Sleep t - 4, 1 
The accuracy is maintained, but the CPU load is now negligible.

Here is a Task Manager snapshot using

Code: Select all

For i As Ulong = 1 to 20
  delay(1000)
Next
Image

That spike is the exe loading, as always, but the CPU load has fallen to the pre-execution level. The CPU load is now negligible.

With a delay of 1000ms, for example, a 4ms threshold gives, roughly, a 996ms Sleep and a 4ms loop. The system will not bat an eyelid with 4ms of intense activity.

OK, so how do we get Sleep to use a 1ms timer?

At the head of our code we simply add:

Code: Select all

Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(As Ulong=1) As Long
_setTimer
Thats it.

I don't use _setTimer inside of delay() as that would affect the accuracy of the delay – albeit only slightly.

For Windows 10 and later, _setTimer no longer affects a global Windows setting. For more details, see timeBeginPeriod function


I have given the new delay() a hammering using fxm's test code, and the new threshold of 4ms appears to be robust. I did not expect an issue because 4ms really is playing it safe.

New delay()

Code: Select all

Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(As Ulong=1) As Long
_setTimer
 
Sub delay(Byval t As Single)  '' delay 't' expressed in milliseconds
Dim As Double t1 = Timer
Dim As Double t2
Dim As Double t3 = t1 + t / 1000
  If t > 4 Then Sleep t - 4, 1
  Do
  #ifndef __FB_WIN32__
    t2 = Timer
    If t2 < t1 Then t1 -= 24 * 60 * 60 : t3 -= 24 * 60 * 60
    Loop Until t2 >= t3
  #else
    Loop Until Timer >= t3
  #endif
End Sub
We have now dropped from a 300ms loop to a 32ms loop and now to a 4ms loop.

So with the above, fxm's delay() is blindingly accurate with an acceptable delay down to 0.1ms with a CPU load which is, quite frankly, not worth mentioning.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

On May 22, 2023 15:43 fxm published 'Example of use ('framecounter()' from dodicat):'

I executed that reducing the fps to 60 and used the given:

Code: Select all

If dt >= 32 Then Sleep dt - 32, 1
I then added:

Code: Select all

Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(As Ulong=1) As Long
_setTimer
and edited:

Code: Select all

If dt >= 4 Then Sleep dt - 4, 1
On my desktop I have several metrics displayed, with one as 'CPU Usage'.

This is what I got:
Image
The top usage uses a 32ms threshold and the bottom a 4ms threshold.

Task Manager refers to the 32ms threshold as 'Very high' power usage. 15.8% is expensive. That is in line with my system backup application, which completes in just over four minutes.

The 4ms threshold varied from low to moderate power usage, said Task Manager. 4.1% is not exactly cheap, but it is not bad.

So associating Sleep with a 1000Hz timer and using a threshold of 4ms dramatically reduces the CPU usage.

Added: The 1ms association is cancelled by Windows when the application closes. MSDN does not mention that.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

deltarho[1859] wrote: May 25, 2023 3:30 On May 22, 2023 15:43 fxm published 'Example of use ('framecounter()' from dodicat):'
The date/time depends on the country, so it is more efficient to provide a link to the post.

Otherwise, I find proportional results (23% and 6%).
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

fxm wrote:The date/time depends on the country, so it is more efficient to provide a link to the post.
We live and learn. :)

23% :!: Flaming heck – your CPU is being throttled.

6% is still high. I looked at a threshold of 2ms and that worked, but I think we are pushing our luck with that.

I am fairly confident that 3ms should be OK and that may help with your machine.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

For debugging purposes, it would be interesting if 'regulate()' could return the delay applied (the one added in the initial loop).
For this, 'regulate()' could be defined as a function (which we could also call as a Sub if we do not want to use this information).

Our previous example with 60 FPS and 3 ms as threshold (for Windows):

Code: Select all

Declare Function _setTimer Lib "winmm" Alias "timeBeginPeriod"(Byval As Ulong = 1) As Long
_setTimer()

Function regulate(Byval MyFps As Long) As Double  '' return delay applied in milliseconds
    Static As Double timervalue
    Dim As Single tt = 1 / MyFps
    Dim As Double tf = timervalue + tt
    Dim As Double t = Timer
    If t < timervalue Then timervalue -= 24 * 60 * 60 : tf -= 24 * 60 * 60
    Dim As Single dt = (tt - (t - timervalue)) * 1000
    If dt >= 3 Then Sleep dt - 3, 1
    Do
        t = Timer
        If t < timervalue Then timervalue -= 24 * 60 * 60 : tf -= 24 * 60 * 60
    Loop Until t >= tf
    timerValue = Timer
    Return dt
End Function

Function framecounter() As Ulong
    Dim As Double t2 = Timer
'    Static As Ulongint t3, frames, answer
    Static As Double t3
    Static As Ulongint frames, answer
    frames += 1
    If (t2 - t3) >= 1 Then
        t3 = t2
        answer = frames
        frames = 0
    End If
    Return answer
End Function

Screen 12
Dim As Ulong MyFps = 60
Do
    Static As Ulongint l
    Static As Single dt
    Screenlock
    Cls
    Color 11
    Print "Requested FPS : "; MyFps
    Print "Measured FPS  : "; framecounter()
    Print "Applied delay :"; dt; " ms"
    Print
    Print
    Print
    Color 14
    Print "<+>      : Increase FPS"
    Print "<->      : Decrease FPS"
    Print "<Escape> : Quit"
    Line (0, 64)-(l, 80), 15, BF
    Screenunlock
    l = (l + 1) Mod 640
    Dim As String s = Inkey
    Select Case s
    Case "+"
        If MyFps < 1000 Then MyFps += 1
    Case "-"
        If MyFps > 10 Then MyFps -= 1
    Case Chr(27)
        Exit Do
    End Select
    dt = regulate(MyFps)
Loop
Last edited by fxm on May 26, 2023 12:16, edited 1 time in total.
Reason: Corrected 'framecounter()' procedure.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

Nice. :)

3ms is a walk in the park on my machine.

Just for the record, here are the Windows APIs affected by TimeBeginPeriod according to a PowerBASIC post of mine in 2014. Yep, I have been using it for a few years now.

Sleep
SleepEx
timeGetTime
timeGetSystemTime
timeSetEvent

All of which have a resolution of 15.625ms without TimeBeginPeriod.

The system revoking TimeBeginPeriod with a process termination is from a comment by Mark Russinovich of SysInternals fame; noted in the PowerBASIC post mentioned above.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

32ms System clock versus 3ms 1000Hz timer.
Image

The top image is, obviously, 32ms looking like WWIII and the bottom image, 3ms, looks like my machine is on tranquillizers.

With the top image, Windows is doing its best to stop the third core being hammered, but we are talking about a single thread of execution, so it cannot do much. With the bottom image, Windows has its feet up enjoying a cup of tea saying: Thanks boss – much better than that horrible PractRand you were running the other week which makes my liquid cooler sweat."

:)

I know – I can stop the cursor being captured, but I keep forgetting. (Senile decay)

Added: Task Manager is reporting the 3ms 1000Hz timer 'Power usage' as being low. :D
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: FreeBasic's Timer

Post by fxm »

About the 'framecounter()' procedure:

Code: Select all

Function framecounter() As Ulong
    Dim As Double t2 = Timer
    Static As Ulongint t3, frames, answer
    frames += 1
    If (t2 - t3) >= 1 Then
        t3 = t2
        answer = frames
        frames = 0
    End If
    Return answer
End Function

An instantaneous measurement (by measuring the frame time) could be provided through a simpler 'framerate()' procedure as follows:

Code: Select all

Function framerate() As Ulong
    Dim As Double t2 = Timer
    Static As Double t1
    Dim As Ulong dt = 1 / (t2 - t1)
    t1 = t2
    Return dt
End Function
but this last procedure uses a division which would add an extra delay in the loop.

But now, about the 'framecounter()' procedure, I am surprised that nobody reacted to it on the use of a 'Ulongint' variable (careless mistake) to memorize the Timer value ('t3') ?
Depending on the requested FPS value, the measurement is either very good or completely wrong for certain values.
Change its type to 'Double' and you will see the difference on the measured FPS value !
(for example on code at: viewtopic.php?p=298857#p298857), but the measurement is now a little less stable.
To have a reliable measurement, one must use a corrected 'framecouner()' procedure or the 'framerate()' procedure.
deltarho[1859]
Posts: 4310
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: FreeBasic's Timer

Post by deltarho[1859] »

fxm wrote:but this last procedure uses a division which would add an extra delay in the loop.
That is true and would have an effect on the 'Applied delay' but 'framerate()' is a simpler procedure so gets some compensation for that. On testing framecounter() versus framerate() the 'Applied delay' differs only in the microsecond range, so the effect on the loop is negligible. The 'Measured FPS' is now immediate.

Added: Of course, if we use regulate() as a Sub we wouldn't use framerate() anyway. :)

------------------------------------------
I am surprised that nobody reacted to it on the use of a 'Ulongint' variable (careless mistake) to memorize the Timer value ('t3')
We should not categorize nations as having a particular personality trait as we are all individuals, but there appears to be some merit in doing just that. So, it is with peer support forums. It was no surprise to me that nobody reacted to your 'careless mistake'. This thread is like many others where they eventually become a discussion between two members. This can happen at PowerBASIC, but there we usually have eight or nine members participating until the thread's conclusion.

When I came here in 2017, PowerBASIC had about 250 active members – it now has, today, 139. However, those 139 are very active. Here the active membership, it seems to me, is very much less and there appears to be a reluctance for many members to participate. Why? I have no idea.

One day, I will write a book entitled "On the psychology of peer support forums". :)

I should add that I missed the UlongInt 'mistake' as I only gave framecounter() a cursory glance; concentrating on the CPU Load.

It looks like my 4ms threshold was playing it too safe. On further analysis, the 3ms threshold should never be an issue.

Added: I hadn't thought about this, but the 3ms threshold relies upon a 1000Hz timer – two thirds of the forum?

I should point out that before Windows 10 'timeBeginPeriod' was a global setting, and we should think about using 'timeEndPeriod' immediately after we are finished using the timer services rather than wait for the process to close. To that end, we need 'Declare Function _freeTimer Lib "winmm" Alias "timeEndPeriod"(As Ulong=1) As Long'.

-----------------------------------------

Requests

For me, your delay() and regulate() procedures are now the definitive procedures for delaying and regulating.

At some point, this thread will disappear into the ether. That should not be the fate of delay() and regulate() so I propose that their code be put into the manual. Since you are the manual's editor-in-chief, I will leave it up to you as to where they should be placed.

Now how about this as a radical suggestion. I firmly believe that your two procedures are so powerful they should be included as keywords in the fbc; delay( amount ) and regulate( amount ). The onus now is on coderJeff. :)

Added: We are, of course, only talking about Windows. I have no idea if Linux, or anybody else for that matter, has a 1000Hz timer, and they will not have time<whatever>Period. coderJeff may baulk at that, and I would not disagree.
Post Reply