Jump to content

1ms loop time possible?


weirddave
 Share

Recommended Posts

I want to have a loop time of 1ms, but I can't work out how to do it.

Obviously there's the high precision timer:

Func _HighPrecisionSleep($iSleep)
    DllCall($hDll_ntdll, "dword", "NtDelayExecution", "int", 0, "int64*", -10 * $iSleep)
EndFunc

But, that only gives me the correct sleep time, I need to be able to measure the time that has passed to subtract from 1ms.  Is there a function I've not found? Obviously I can't use the autoit timer functions....

Link to comment
Share on other sites

Here is an example of using a timer to fix your loop time manually. You might find your cpu usage is very high if you run this constantly. This example stores the timer in an array for 100 iterations, and it was sometimes out by 10% but was usually pretty close. 

#include <Array.au3>

Local $a[100]


Local $i = 1
$hTimer = TimerInit()
While 1
    Do
    Until TimerDiff($hTimer) > $i

    ; 1ms loop
    $a[$i] = TimerDiff($hTimer)
    If $i >= 99 Then ExitLoop


    $i += 1
WEnd


_ArrayDisplay($a)

Mat

Link to comment
Share on other sites

Barring everything else, OS multitasking will void attemps to precise and reproductible 1 ms loops, unless very special coding.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

14 minutes ago, weirddave said:

This will work, but is a complete cpu hog. I hadn't realised the timer was so accurate though, so I may be able to use the high precision sleep in conjunction with this :)


 

Yep, seems even a 100ns sleep is enough to reduce the cpu usage to pretty much zero. It doesn't seem to impact too badly on the accuracy of the loop.

#include <Array.au3>

Local $sum, $loops = 10000

Local $hDll_ntdll = DllOpen("ntdll.dll")

Local $prev = 0
Local $i = 1, $diff
$hTimer = TimerInit()
While 1
    Do
        DllCall($hDll_ntdll, "dword", "NtDelayExecution", "int", 0, "int64*", -1)
        $diff = TimerDiff($hTimer)
    Until $diff > $i

    ; 1ms loop
    $sum += $diff - $prev
    $prev = $diff
    If $i >= $loops Then ExitLoop


    $i += 1
WEnd

MsgBox(0, "Finished.", "Mean loop time: " & ($sum / $loops))

 

Link to comment
Share on other sites

I've pieced together some test code which stores the 'spare' time in a 2nd array. Everything is fine for the first 500, then something odd happens.

I made the delay 900us to let the TimerDiff have the last 10%, should be more accurate?
 

#include <Array.au3>
Global $hDll_ntdll = DllOpen("ntdll.dll")
Local $a[10000]
Local $b[10000]

Local $i = 1
$hTimer = TimerInit()
While 1
    $t = TimerDiff($hTimer)
    $a[$i] = $t
    $t2 = 1000-int(($t-int($t))*900)
    $b[$i]=$t2
    _HighPrecisionSleep($t2)
    Do
    Until TimerDiff($hTimer) >= $i

    ; 1ms loop
    $a[$i] = TimerDiff($hTimer)
    If $i >= 9999 Then ExitLoop


    $i += 1
WEnd
msgbox(0,"",TimerDiff($hTimer))

_ArrayDisplay($a)
_ArrayDisplay($b)


Func _HighPrecisionSleep($iSleep)
    DllCall($hDll_ntdll, "dword", "NtDelayExecution", "int", 0, "int64*", -10 * $iSleep)
EndFunc
Edited by weirddave
added a comment
Link to comment
Share on other sites

18 minutes ago, Mat said:

Yep, seems even a 100ns sleep is enough to reduce the cpu usage to pretty much zero. It doesn't seem to impact too badly on the accuracy of the loop.

Since CPUs are 3GHz+ these days, a few instructions is only 1% of 100ns. The CPU I'm running this on has 6 cores, it doesn't even reach 1% (I suspect it would for 1 core).

I think we have a winner :)

 

Edited by weirddave
Link to comment
Share on other sites

After much testing, it seems there is a problem with the DllCall. When I run the code in post #5, it seems to work until it doesn't, I now get a mean loop time of anywhere between 5 and 10. Commenting out the DllCall gives a result of 1 as expected. Rebooting doesn't seem to help.
Could changes in clock speed of the CPU (due to speedstep) be causing the error? (I don't think this is the cause, I just haven't been able to rule it out yet)

 

Link to comment
Share on other sites

I modified the code slightly to count the number of times the do until loop completes:

#include <Array.au3>

Local $sum, $loops = 10000

Local $hDll_ntdll = DllOpen("ntdll.dll")

Local $prev = 0
Local $i = 1, $diff
$hTimer = TimerInit()
$loopcount=0
While 1
    Do
        DllCall($hDll_ntdll, "dword", "NtDelayExecution", "int", 0, "int64*", -1)
        $diff = TimerDiff($hTimer)
        $loopcount+=1
    Until $diff > $i

    ; 1ms loop
    $sum += $diff - $prev
    $prev = $diff
    If $i >= $loops Then ExitLoop


    $i += 1
WEnd

MsgBox(0, "Finished.", "Mean loop time: " & ($sum / $loops))
MsgBox(0, "", "Loop Count : " & $loopcount)

Without the DllCall, it completes 10.8M times, with the DllCall, 10000 times, the same as the number of loops! This tells me it's taking at least 1ms to do the call :(
This has worked previously, can anyone else confirm the problem? (or spot my keyboard accident?)

Link to comment
Share on other sites

I get 19836, so roughly twice per iteration. Whilst not ideal, it's better than your result.

As Jchd mentioned, the OS will chooses who gets execution time or not, so it's difficult to get repeatable results.

There's not much you can do to improve the overheads in that loop. About the only thing I could think to do would be to save one call to GetProcAddress and store it. It didn't make that much of a difference (19910 loops). Interestingly I see no difference at all between 32 and 64 bit versions.

Only other thing I can think to try is to write a small routine to do the same thing in a compiled language, load it into memory and DllCallAddress it. That would minimise the time spent executing code in that loop. It's a fair bit of effort to get working though.

Link to comment
Share on other sites

Interesting. What result do you get if you comment out the DllCall?

This is quite frustrating as it was working fine yesterday. I assumed that I'd messed something up with all my testing and so I rebooted, but no change.

Writing the code in C then figuring out how to load it and call it is a bit advanced for me at the moment, it would be easier to just do it all in C :D 

 

Link to comment
Share on other sites

Well, that's an eye opener. From that we can see only 6608 times the loop with the DLL call managed to fit in a 2nd call (I admit, some might have managed 3 or more and so less actually managed multiple calls). Given that this is supposed to take 100ns, it looks like something is broke with that particular call.

 

Link to comment
Share on other sites

DllCall has a lot to do, in and out, and isn't the fastest function in the world.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

I tried to get it doing a bit less by using DllCallAddress:

#include <Array.au3>
#include <WinAPI.au3>

Local $sum, $loops = 10000

Local $hDll_ntdll = _WinAPI_LoadLibrary("ntdll.dll")
Local $pNtDelayExecution = _WinAPI_GetProcAddress($hDll_ntdll, "NtDelayExecution")

Local $prev = 0
Local $i = 1, $diff
$hTimer = TimerInit()
$loopcount=0
While 1
    Do
        DllCallAddress("dword", $pNtDelayExecution, "int", 0, "int64*", -1)
        $diff = TimerDiff($hTimer)
        $loopcount+=1
    Until $diff > $i

    ; 1ms loop
    $sum += $diff - $prev
    $prev = $diff
    If $i >= $loops Then ExitLoop


    $i += 1
WEnd

MsgBox(0, "Finished.", "Mean loop time: " & ($sum / $loops))
MsgBox(0, "", "Loop Count : " & $loopcount)

_WinAPI_FreeLibrary($hDll_ntdll)

Very minor improvement. It's also not going to get much better without writing the whole loop another way.

Loop Count: 17562

 

Link to comment
Share on other sites

If I run this code I get loop times around 10 ms.

;#AutoIt3Wrapper_UseX64 = y
$iLoops = 100
$hTimer = TimerInit()
For $i = 1 To $iLoops
  DllCall( "ntdll.dll", "dword", "NtDelayExecution", "int", 0, "int64*", -1 ) ; Steps = 100 ns
Next
MsgBox( 0, "", "Mean loop time: " & ( TimerDiff( $hTimer ) / $iLoops ) )

The NtDelayExecution function is not working. The function seems to stem from Windows 2000 and it has obviously worked in this version. But it does not work in newer Windows versions, where it seems not possible to set a smaller sleep time than 1 ms.
 

You don't have to search long before you find the functions timeBeginPeriod and timeEndPeriod. With these functions it's possible to get 1 ms loops:

;##AutoIt3Wrapper_UseX64 = y
$fSum = 0
$iLoops = 1000
For $i = 1 To 10
  $hTimer = TimerInit()
  For $j = 1 To $iLoops
    DllCall( "winmm.dll", "dword", "timeBeginPeriod", "int", 1 )                    ; Steps = 1 ms
    DllCall( "ntdll.dll", "dword", "NtDelayExecution", "int", 0, "int64*", -10000 ) ; Steps = 100 ns (10000 * 100 ns = 1 ms)
    DllCall( "winmm.dll", "dword", "timeEndPeriod", "int", 1 )                      ; Steps = 1 ms
  Next
  $fDiff = TimerDiff( $hTimer )
  ConsoleWrite( "Mean loop time: " & ( $fDiff / $iLoops ) & @CRLF )
  $fSum += $fDiff
Next
ConsoleWrite( "Average mean loop time: " & ( $fSum / ( 10 * $iLoops ) ) & @CRLF )
;##AutoIt3Wrapper_UseX64 = y
$fSum = 0
$iLoops = 1000
For $i = 1 To 10
  $hTimer = TimerInit()
  For $j = 1 To $iLoops
    DllCall( "winmm.dll", "dword", "timeBeginPeriod", "int", 1 ) ; Steps = 1 ms
    DllCall( "kernel32.dll", "none", "Sleep", "int", 1 )         ; Steps = 1 ms
    DllCall( "winmm.dll", "dword", "timeEndPeriod", "int", 1 )   ; Steps = 1 ms
  Next
  $fDiff = TimerDiff( $hTimer )
  ConsoleWrite( "Mean loop time: " & ( $fDiff / $iLoops ) & @CRLF )
  $fSum += $fDiff
Next
ConsoleWrite( "Average mean loop time: " & ( $fSum / ( 10 * $iLoops ) ) & @CRLF )

But you cannot add much code to these loops before it is no longer 1 ms loops. And you'll probably rather quickly see an increase in the CPU usage.

Link to comment
Share on other sites

There's a problem with displaying the average, it's the average :D 

I added a bit of code to output the longest delay, it's 10ms on this particular PC

I will have to go for the CPU hungry TimerDiff() loop.

 

I strongly suspect that when I add the rest of the code to deal with the GUI, 1ms will get wiped out. Do you think it's worth me ditching the attempt to single thread this or should I launch the GUI part as a separate process and pass messages to the time sensitive process as required?

The time sensitive part will be talking to some hardware which expects UDP data every 1ms, but it doesn't need to change every time.

Link to comment
Share on other sites

If you're serious about a repetitive, precise 1ms timing/action, then you should read and understand at least the juice of this page https://www.microsoftpressstore.com/articles/article.aspx?p=2233328&seqNum=7

Agreed it's dissecting Vista and not the later OSes but this should give you a grip of what you can expect in the area of precise timing associated with some action. The raw moral is that you just can't expect anything precise in the ms range without allowing from "from time to time" to "very often" gaps in the ms schedule. Only genuine "real-time" OSes (RTOS, twiked Un*ces, ...) can give you the certitude of the precise regularity you call for, but only after taking great care about what you ask the OS/hardware to do.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...