weirddave

1ms loop time possible?

17 posts in this topic

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....

Share this post


Link to post
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

1 person likes this

Share this post


Link to post
Share on other sites

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 :)


 

Share this post


Link to post
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)

Share this post


Link to post
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))

 

1 person likes this

Share this post


Link to post
Share on other sites

#6 ·  Posted (edited)

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

Share this post


Link to post
Share on other sites

#7 ·  Posted (edited)

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

Share this post


Link to post
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)

 

Share this post


Link to post
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?)

Share this post


Link to post
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.

Share this post


Link to post
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 

 

Share this post


Link to post
Share on other sites

On my laptop at home so different results from work PC:

DllCall - Loop Count: 16608
Sans-DllCall  - Loop Count: 12234691

 

Share this post


Link to post
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.

 

Share this post


Link to post
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)

Share this post


Link to post
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

 

Share this post


Link to post
Share on other sites

I had a play with:

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

DllCall($Dll, "none", "Sleep", "long", 1)

it's milliseconds but supposedly you can change the resolution of the time. Doesn't look hopeful either tho.

Back to the CPU hogging method for now then :(

Thanks for the help and testing efforts.

Share this post


Link to post
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.

Share this post


Link to post
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