[Solved] BlitzMax microseconds functions

Started by Midimaster, August 04, 2021, 07:38:18

Previous topic - Next topic

Midimaster

*** EDIT ***

there is a ready to use code in post #9:



  • Enables UDelay() in BlitzMax 1.50 too

  • Enables MicroTime-stamps in both BlitzMax

  • Enables microsecond exact wake up in both BlitzMax

  • Enables measurements accurate to the microsecond in both BlitzMax




In BlitzMax NG there are is a  functions UDelay, which enable microsecond waiting.  Now I would like to find out how this is made. My idea is: Perhaps it is a Windows API function and can be enabled also in old BlitzMax 1.50

I search the files and folders inside C:\BlitzMaxNG/mod/brl.mod/blitz.mod and inside C:\BlitzMaxNG\MinGW32x64\x86_64-w64-mingw32\include\sys to find the real (deepest) source code of this function.

But I'm not very used in C and GNU and now struggle with the structure of the source files *.bmx or *.h or *.c and so on. It looks like they all only reference to a still deeper function.

Does somebody know where to search? Or does somebody know how USleep()  is made?


In blitz.bmx there is only a:
Function UDelay( microseconds:Int )="void bbUDelay(int)!"

...and over 40 Import "xxx.c" lines

In blitz_app.c I found:
void bbDelay( int millis ){
if (millis<0) return;
usleep( millis*1000 );
}

#if __STDC_VERSION__ >= 199901L
extern void bbUDelay( int microseconds );
#else
void bbUDelay( int microseconds ) {
if (microseconds <0) return
usleep( microseconds );
}
#endif


in unstd.h I found:
#if !defined __NO_ISOCEXT
#include <sys/types.h> /* For useconds_t. */

int __cdecl __MINGW_NOTHROW usleep(useconds_t);
#endif  /* Not __NO_ISOCEXT */


so I got finally "lost in sourcefiles"....




...on the way to Egypt

Henri

Hi,

it is easy to get lost in the source :-)

This should work for both. Note that usleep has maximum parameter value limit of 999 999 microseconds, which after that it doesn't work.

Code (blitzmax) Select


Extern "C"
Function usleep:Int(microsecs:Int)
EndExtern

Print "Waiting..."

Local start:Int = MilliSecs()

'Delay for 5000 microsecs = 5 milliseconds
usleep(5000)

Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."


-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

#2
sorry but it look like this code is not working as expected

Extern "C"
        Function usleep:Int(microsecs:Int)
EndExtern

Print "Waiting..."

Local start:Int = MilliSecs()

For Local i%=0 To 9
usleep(500)
Print "tick"
Next
Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."


Returns 20msec on a BlitzMax 1.50 as result. I would have expected 5msec.

Even on BlitzMax NG is seems not to work. Here it returns 0msec.

When I replace usleep with UDelay on BlitzMax NG ist return correct 5msec


I also already know the QueryPerformanceTimer(). It can be used as a workaround for pauses of microseconds. But this approach burns the time instead of sleeping and returning to the system.
...on the way to Egypt

Henri

It is hard to measure microseconds accurately. In your loop there are additional components that have to be taken into account, like the print function and the loop itself.

Also, the system timer resolution is not accurate enough to produce exact delay, but in most case it's accurate enough.

By the way, usleep is standard C function sense C99 and is crossplatform (although there are mentions that it's obsolete, even though it functions perfectly)

-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

#4
The loop...
Local start:Int = MilliSecs()
For Local i%=0 To 9
Print "tick"
Next
Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."

shows as result 0. So it needs below 1 msec. I can write For  i%=0 to 299 and it still needs below 1 msec.

If I write this:
Local start:Int = MilliSecs()
usleep(1)
Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."

it show 2msec. It looks like every call of the usleep() needs 2msec. Instead of value=1 I can use any value from 1 to 1999 it shows always 2msec.

It looks like the function is existing in BlitzMax 1.50 but not implemented correct. Remember that BlitzMax 1.50 does not compile with GCC but FASM!!!

I think I need to find the code of the C-function and some H-files to get this working.
...on the way to Egypt

Henri

I don't think the issue is with usleep. The issue is with millisecs that is not accurate enough to measure fraction of a milliseconds. It rounds those up or down.

If I run this and change the delay on 1000 microsecond intervals (that is 1 millisecond) I get exactly 1 millisecond intervals. If I change the delay to 2000 microseconds I get 2 milliseconds and so on in my Bmax ng:

Code (blitzmax) Select

Extern "C"
        Function usleep:Int(microsecs:Int)
EndExtern

Print "Waiting..."

Local ar:Int[10]

Local now:Int
Local start:Int = MilliSecs()

For Local i:Int = 0 To 9
usleep(1000)
ar[i] = MilliSecs()
Next

For Local t:Int = EachIn ar
Print t + " milliseconds"
Next

Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."


-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

#6
But we are talking about BlitzMax 1.50! Do you really test on 1.50?

In BlitzMax NG I dont need this function, because UDelay works perfect!!!

To eliminate Millisecs()-Timer inaccuracy, I tested with a FOR/NEXT-loop.

100 times a usleep of 50 should result in 5msec. But it gives 200msec!!!
100 times a usleep of 500 should result in 50msec. But it gives 200msec!!!
100 times a usleep of 1000 should result in 100msec. But it gives 200msec!!!

And testing it with a parameter of 1000 or 2000 is not usefull to prove working MicroSeconds. It looks like the usleep() is internaly replaced by a simple Millisecs() call.

Exact your code (parameter=1000) results at my computer in:
45983103 milliseconds
45983105 milliseconds
45983107 milliseconds
45983109 milliseconds
45983111 milliseconds
45983113 milliseconds
45983115 milliseconds
45983117 milliseconds
45983119 milliseconds
45983121 milliseconds
The End. Waiting took 20 milliseconds.


Can you please tell me, what your code example returns when parameter=500?



...on the way to Egypt

Henri

#7
I think I know where the confusion is coming from.

NG is not using standard library usleep in Windows environment for UDelay, only in Linux and Mac.

Instead, it uses a counter:


void bbUDelay( int microseconds ) {
__int64 time1 = 0;
__int64 time2 = 0;
__int64 freq = 0;

QueryPerformanceCounter((LARGE_INTEGER *) &time1);
QueryPerformanceFrequency(&freq);

do {
Sleep(0);
QueryPerformanceCounter((LARGE_INTEGER *) &time2);
} while(time2-time1 < microseconds*freq/1000000);
}


You would only need to adopt this code to make it work in 1.50 for Windows (and use usleep for others). Sounds reasonable ?

-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

#8
Ah! Thanks a lot. That was what I was searching for. I did not found it.

I knew already the QueryPerformanceCounter, but I did not know that I can use it in combination with a Sleep(0).

Sleep(0) could be a solution. I will make some test how exact this is for times below 1msec.
...on the way to Egypt

Midimaster

#9
Microseconds in BlitzMax

This code enable micro second access in both BlitzMax versions. Similar to functions like UDelay(). I added code examples for each function.

Code (BlitzMax) Select

Type MicroTimer
' enables micro-second access in BlitzMax
'
' Functions
' ---------------------------------------------------------------------------------
'     Now:Long()                  returns a timestamp in Microseconds
'
'     SleepUntil(MicroTime:Long)  sleeps until the current time MicroTime is reached
'
'     GetDelta:Long()             returns the MicroSeconds since last GetDelta()-Call
'
'     UDelay(MicroSeconds:Int)     pauses for n MicroSeconds
'
' -------------------------------------------------------------------------------
Global FREQUENCE:Long=0
Global Last:Long=0


Function Now:Long()
' PUBLIC:
' returns a timestamp in Microseconds (1000000 equals 1sec)
'         since the computer has started
'
'         example:
'         ******************************************
'            WhatsTheTime:Long = MicroTimer.Now()
'            PRINT "time is : " + WhatsTheTime
'         ******************************************
'
Local locNow:Long = 0
If FREQUENCE = 0
If Not QueryPerformanceFrequency(Varptr(FREQUENCE)) End
EndIf
QueryPerformanceCounter(Varptr(locNow))
locNow = locNow*1000000/FREQUENCE
Return locNow
End Function



Function SleepUntil(MicroTime:Long)
' PUBLIC:
' sleeps until the current time MicroTime is reached
'
'         example:
'         ******************************************
'            local Later:Long = MicroTimer.Now()+500   ' +0.5msec
'            ' do some code....
'            PRINT "hello"
'            MicroTimer.SleepUntil(Later)              ' now wait
'         ****************************************** 
'
While Now() < MicroTime
If Now()< MicroTime-1000
Delay 1
EndIf
Wend
End Function


Function GetDelta:Long()
' PUBLIC:
' returns the MicroSeconds since last GetDelta()-Call
'
'         example:
'         ******************************************
'            MicroTimer.GetDelta()    ' reset
'            ' now lets check the performace of this code lines....
'            For i=0 to 999
'                PRINT "hello"
'            Next
'            PRINT "code needed " + MicroTimer.GetDelta() + "microseconds"
'         ******************************************

Local locNow:Long = now()
Local Delta:Long  = locNow-Last
Last = locNow
Return Delta
End Function


Function UDelay(MicroSeconds:Int)
' PUBLIC:
' pauses for n MicroSeconds
'
'         example:
'         ******************************************
'            For i=0 to 9
'                PRINT "hello"
'                MicroTimer.UDelay(50)   ' pauses for 0.05msec
'            Next
'         ******************************************

Local locNow:Long = now() + MicroSeconds
SleepUntil locNow
End Function
End Type

Extern "win32"
    Function QueryPerformanceCounter:Int(out:Long Ptr)
    Function QueryPerformanceFrequency:Int(out:Long Ptr)
End Extern



...on the way to Egypt

Henri

Good job.

One observation:

It looks that calling Delay(0) in SleepUntil function will not free system resources as it just seems to return immediately if zero value was passed. You might need to add sleep function and call that instead (sleep(0) or usleep(1) which is the minimum value)

-Henri
- Got 01100011 problems, but the bit ain't 00000001

Midimaster

Oh yes, you are right! It makes no difference wether I use...

While Now() < MicroTime
Delay 0
Wend

or
While Now() < MicroTime
sleep 0
Wend

or
While Now() < MicroTime
' do nothing
Wend


In all three cases the processor goes upto 23%. But the exactness is 100% as expected.

Using a sleep(1) or usleep(1) makes no sense, because then the exactness gets lost.

So it looks like a delay call with value 0 does not really help to save resources. So we still search for a solution below Milliseconds. Maybe that the same happens on BlitzMax NG?


It's the same! Also on BlitzMax NG the processor goes at 25% with this code:

Code (BlitzMax) Select
Graphics 800,600

Repeat
Local start:Int = MilliSecs()
For Local i%=0 To 99
UDelay 50
Next
Print "The End. Waiting took " + (MilliSecs()-start) + " milliseconds."
Until AppTerminate()



***EDIT***

I updated the code in post #9. Now at least it saves performance when the delaytime is bigger than 1000 microseconds.




...on the way to Egypt

Baggey

Cool keep up the good work! I hope this microseconds function will work. I Think this maybe usefull for my SpecBlitz emulator and the RingBuffer!  :)

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!