weirddave Posted September 19, 2016 Share Posted September 19, 2016 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 More sharing options...
Mat Posted September 19, 2016 Share Posted September 19, 2016 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 weirddave 1 AutoIt Project Listing Link to comment Share on other sites More sharing options...
weirddave Posted September 19, 2016 Author Share Posted September 19, 2016 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 Link to comment Share on other sites More sharing options...
jchd Posted September 19, 2016 Share Posted September 19, 2016 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 hereRegExp tutorial: enough to get startedPCRE 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 More sharing options...
Mat Posted September 19, 2016 Share Posted September 19, 2016 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)) weirddave 1 AutoIt Project Listing Link to comment Share on other sites More sharing options...
weirddave Posted September 19, 2016 Author Share Posted September 19, 2016 (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 September 19, 2016 by weirddave added a comment Link to comment Share on other sites More sharing options...
weirddave Posted September 19, 2016 Author Share Posted September 19, 2016 (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 September 19, 2016 by weirddave Link to comment Share on other sites More sharing options...
weirddave Posted September 21, 2016 Author Share Posted September 21, 2016 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 More sharing options...
weirddave Posted September 21, 2016 Author Share Posted September 21, 2016 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 More sharing options...
Mat Posted September 21, 2016 Share Posted September 21, 2016 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. AutoIt Project Listing Link to comment Share on other sites More sharing options...
weirddave Posted September 21, 2016 Author Share Posted September 21, 2016 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 Link to comment Share on other sites More sharing options...
Mat Posted September 21, 2016 Share Posted September 21, 2016 On my laptop at home so different results from work PC: DllCall - Loop Count: 16608 Sans-DllCall - Loop Count: 12234691 AutoIt Project Listing Link to comment Share on other sites More sharing options...
weirddave Posted September 21, 2016 Author Share Posted September 21, 2016 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 More sharing options...
jchd Posted September 21, 2016 Share Posted September 21, 2016 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 hereRegExp tutorial: enough to get startedPCRE 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 More sharing options...
Mat Posted September 21, 2016 Share Posted September 21, 2016 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 AutoIt Project Listing Link to comment Share on other sites More sharing options...
weirddave Posted September 22, 2016 Author Share Posted September 22, 2016 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. Link to comment Share on other sites More sharing options...
LarsJ Posted September 24, 2016 Share Posted September 24, 2016 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. Controls, File Explorer, ROT objects, UI Automation, Windows Message MonitorCompiled code: Accessing AutoIt variables, DotNet.au3 UDF, Using C# and VB codeShell menus: The Context menu, The Favorites menu. Shell related: Control Panel, System Image ListsGraphics related: Rubik's Cube, OpenGL without external libraries, Navigating in an image, Non-rectangular selectionsListView controls: Colors and fonts, Multi-line header, Multi-line items, Checkboxes and icons, Incremental searchListView controls: Virtual ListViews, Editing cells, Data display functions Link to comment Share on other sites More sharing options...
weirddave Posted September 26, 2016 Author Share Posted September 26, 2016 There's a problem with displaying the average, it's the average 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 More sharing options...
Mat Posted September 26, 2016 Share Posted September 26, 2016 To be honest, I think you're better off changing language and doing the timed part in a lower level language. You can use PostMessage with a user message code to communicate significant events back to the UI thread. AutoIt Project Listing Link to comment Share on other sites More sharing options...
jchd Posted September 26, 2016 Share Posted September 26, 2016 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 hereRegExp tutorial: enough to get startedPCRE 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 More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now