*** EDIT ***
there is a ready to use code in post #9:
Microseconds in BlitzMax
https://www.syntaxbomb.com/blitzmax-blitzmax-ng/usleep/msg347051757/#msg347051757 (https://www.syntaxbomb.com/blitzmax-blitzmax-ng/usleep/msg347051757/#msg347051757)
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"....
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.
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
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.
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
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.
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:
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
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?
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
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.
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.
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
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
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:
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.
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