Forum > Windows
Windows Sleep precision
440bx:
Hello,
Some of you may remember a somewhat recent "discussion" about the accuracy, or lack thereof, of the Sleep() API.
Some forum members did a few tests on machines they had available and the results seemed to vary quite a bit. I decided to investigate the issue.
On real hardware, I get very accurate times. In VMs, the results were "erratic" to put it kindly but, they can be made very close to being as precise as on real hardware. The "trick" is to set the timer resolution using undocumented ntdll.dll APIs.
Here is a little program that queries the timer resolution _and_ "optionally" sets the timer to the maximum resolution. After setting the timer to the maximum resolution, the Sleep times are very accurate even in a VM.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$APPTYPE CONSOLE} program _NtSetTimerResolution; uses Windows, Sysutils ; type NTSTATUS = DWORD; const ntdll = 'ntdll'; function NtSetTimerResolution ( { _in_ } InDesiredTime : DWORD; { _in_ } InSetResolution : boolean; { _out_ } OutActualTime : PDWORD ) : NTSTATUS; stdcall; external ntdll; function NtQueryTimerResolution ( { _out_ } OutMinimumResolution : PDWORD; { _out_ } OutMaximumResolution : PDWORD; { _out_ } OutCurrentResolution : PDWORD ) : NTSTATUS; stdcall; external ntdll; const STATUS_OK = 0; var MaximumResolution, MinimumResolution, CurrentResolution : DWORD; Status { NTSTATUS } : DWORD; begin writeln; writeln(' NtSetTimerResolution'); writeln; writeln(' sets the timer resolution to maximum resolution possible'); writeln; MinimumResolution := 0; MaximumResolution := 0; CurrentResolution := 0; Status := NtQueryTimerResolution(@MinimumResolution, @MaximumResolution, @CurrentResolution); repeat if Status <> STATUS_OK then begin writeln(' call to NtQueryTimerResolution failed. NTSTATUS: ', IntToHex(Status, 0)); break; end; writeln; writeln(' Minimum Resolution : ', MinimumResolution); writeln(' Maximum Resolution : ', MaximumResolution); writeln(' Current Resolution : ', CurrentResolution); writeln; writeln(' press ENTER/RETURN to set the resolution to the maximum'); writeln(' resolution or CTRL-C to terminate without setting it.'); readln; { for good measure, reset the current resolution variable } CurrentResolution := 0; Status := NtSetTimerResolution(MaximumResolution, TRUE, @CurrentResolution); if Status <> STATUS_OK then begin writeln(' call to NtSetTimerResolution failed. NTSTATUS: ', IntToHex(Status, 0)); break; end; writeln; writeln(' NtSetTimerResolution returned CurrentResolution : ', CurrentResolution); writeln; { redo the call to NtQueryTimerResolution } MinimumResolution := 0; MaximumResolution := 0; CurrentResolution := 0; Status := NtQueryTimerResolution(@MinimumResolution, @MaximumResolution, @CurrentResolution); writeln(' NtQueryTimerResolution now reports : '); writeln; writeln(' Minimum Resolution : ', MinimumResolution); writeln(' Maximum Resolution : ', MaximumResolution); writeln(' Current Resolution : ', CurrentResolution); until TRUE; writeln; writeln('press ENTER/RETURN to end this program'); readln;end. However, there is an interesting "peculiarity" concerning the bitness of the program.
if a 32bit version of the above code is run on a 64bit O/S then the timer resolution seems to apply only to the process, however, if a 64bit version of the above code is run on the same 64bit O/S (real hardware or VM, doesn't matter), then the resolution is set "globally" and persists after the program has ended (even in a VM.)
It seems that 32bit programs running on a 64bit O/S are genuine "second class" citizens.
Enjoy.
PS: personally, I have not experienced _any_ negative side effects from setting the resolution to the maximum. On the contrary, after setting the resolution, Sleep is quite accurate, which is definitely welcome.
PPS: Sysinternals' Clockres utility is implemented using "NtQueryTimerResolution" just as the above program is but, unlike the program above, it does not allow the user to set the timer resolution.
rvk:
Have you tried to put a
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TimeBeginPeriod(1);in your code instead of the NtSetTimerResolution?
(I already mentioned and showed that in the other topic)
And what versions of 32 and 64 where you testing for the global issue?
It was also discussed in the other topic that Windows 10 < version 2004 this setting was global, later versions not.
440bx:
--- Quote from: rvk on July 21, 2022, 10:17:27 am ---Have you tried to put a
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TimeBeginPeriod(1);in your code instead of the NtSetTimerResolution?
--- End quote ---
No, I haven't tried that and, disassembly of that function shows it's just a roundabout way of calling NtSetTimerResolution. The disassembly of that function is:
--- Code: ASM [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---.text:000007FF71CCA648 ; MMRESULT __stdcall timeBeginPeriod(UINT uPeriod).text:000007FF71CCA648 public timeBeginPeriod.text:000007FF71CCA648 timeBeginPeriod proc near ; CODE XREF: timeSetEventInternal+A7↓p.text:000007FF71CCA648 ; midiOutTimerTick+1CA↓p.text:000007FF71CCA648 ; DATA XREF: ....text:000007FF71CCA648.text:000007FF71CCA648 ActualResolution= dword ptr 8.text:000007FF71CCA648 arg_8 = qword ptr 10h.text:000007FF71CCA648 arg_10 = qword ptr 18h.text:000007FF71CCA648.text:000007FF71CCA648 ; FUNCTION CHUNK AT .text:000007FF71CCD636 SIZE 0000008A BYTES.text:000007FF71CCA648.text:000007FF71CCA648 mov [rsp+arg_8], rbx.text:000007FF71CCA64D mov [rsp+arg_10], rbp.text:000007FF71CCA652 push rdi.text:000007FF71CCA653 push r12.text:000007FF71CCA655 push r13.text:000007FF71CCA657 sub rsp, 20h.text:000007FF71CCA65B mov edi, ecx.text:000007FF71CCA65D cmp ecx, cs:TDD_MAXRESOLUTION.text:000007FF71CCA663 jb loc_7FF71CCD636.text:000007FF71CCA669 cmp ecx, cs:uPeriod.text:000007FF71CCA66F jnb loc_7FF71CCD640.text:000007FF71CCA675 lea rcx, ResolutionCritSec ; lpCriticalSection.text:000007FF71CCA67C call cs:__imp_EnterCriticalSection.text:000007FF71CCA682 mov r11d, edi.text:000007FF71CCA685 lea rbp, TimerData.text:000007FF71CCA68C sub r11d, cs:TDD_MAXRESOLUTION.text:000007FF71CCA693 mov r13d, 0FFFFh.text:000007FF71CCA699 movzx eax, word ptr [rbp+r11*2+46h].text:000007FF71CCA69F cmp ax, r13w.text:000007FF71CCA6A3 jz loc_7FF71CCD647.text:000007FF71CCA6A9 inc ax.text:000007FF71CCA6AC mov [rbp+r11*2+46h], ax.text:000007FF71CCA6B2 cmp ax, 1.text:000007FF71CCA6B6 jnz loc_7FF71CCD6B9.text:000007FF71CCA6BC cmp edi, cs:dword_7FF71CEE3FC.text:000007FF71CCA6C2 jnb loc_7FF71CCD6B9.text:000007FF71CCA6C8 mov rcx, cs:WPP_GLOBAL_Control.text:000007FF71CCA6CF lea r12, WPP_GLOBAL_Control.text:000007FF71CCA6D6 cmp rcx, r12.text:000007FF71CCA6D9 jz short loc_7FF71CCA6EC.text:000007FF71CCA6DB bt dword ptr [rcx+1Ch], 16h.text:000007FF71CCA6E0 jnb short loc_7FF71CCA6EC.text:000007FF71CCA6E2 cmp byte ptr [rcx+19h], 5.text:000007FF71CCA6E6 jnb loc_7FF71CCD656.text:000007FF71CCA6EC.text:000007FF71CCA6EC loc_7FF71CCA6EC: ; CODE XREF: timeBeginPeriod+91↑j.text:000007FF71CCA6EC ; timeBeginPeriod+98↑j ....text:000007FF71CCA6EC mov eax, cs:MinimumTime.text:000007FF71CCA6F2 mov ecx, edi.text:000007FF71CCA6F4 lea r8, [rsp+38h+ActualResolution] ; ActualResolution.text:000007FF71CCA6F9 imul ecx, 2710h.text:000007FF71CCA6FF cmp ecx, eax.text:000007FF71CCA701 mov dl, 1 ; SetOrUnset.text:000007FF71CCA703 cmovb ecx, eax ; RequestedResolution.text:000007FF71CCA706 mov [rsp+38h+ActualResolution], ecx.text:000007FF71CCA70A call cs:__imp_NtSetTimerResolution.text:000007FF71CCA710 xor ebx, ebx.text:000007FF71CCA712 cmp eax, ebx.text:000007FF71CCA714 jl loc_7FF71CCD674.text:000007FF71CCA71A mov ecx, [rsp+38h+ActualResolution].text:000007FF71CCA71E mov eax, 0D1B71759h.text:000007FF71CCA723 mov cs:dword_7FF71CEE3F8, edi.text:000007FF71CCA729 add ecx, 26ACh.text:000007FF71CCA72F mul ecx.text:000007FF71CCA731 shr edx, 0Dh.text:000007FF71CCA734 mov cs:dword_7FF71CEE3FC, edx.text:000007FF71CCA73A jmp short $+2.text:000007FF71CCA73C ; ---------------------------------------------------------------------------.text:000007FF71CCA73C.text:000007FF71CCA73C loc_7FF71CCA73C: ; CODE XREF: timeBeginPeriod+F2↑j.text:000007FF71CCA73C ; timeBeginPeriod+306C↓j ....text:000007FF71CCA73C lea rcx, ResolutionCritSec ; lpCriticalSection.text:000007FF71CCA743 call cs:__imp_LeaveCriticalSection.text:000007FF71CCA749 mov eax, ebx.text:000007FF71CCA74B.text:000007FF71CCA74B loc_7FF71CCA74B: ; CODE XREF: timeBeginPeriod+2FF3↓j.text:000007FF71CCA74B ; timeBeginPeriod+2FFA↓j.text:000007FF71CCA74B mov rbx, [rsp+38h+arg_8].text:000007FF71CCA750 mov rbp, [rsp+38h+arg_10].text:000007FF71CCA755 add rsp, 20h.text:000007FF71CCA759 pop r13.text:000007FF71CCA75B pop r12.text:000007FF71CCA75D pop rdi.text:000007FF71CCA75E retn.text:000007FF71CCA75E ; ---------------------------------------------------------------------------.text:000007FF71CCA75F db 9 dup(90h).text:000007FF71CCA75F timeBeginPeriod endp.text:000007FF71CCA75F.text:000007FF71CCA768 ; Exported entry 138. timeEndPeriod.text:000007FF71CCA768.text:000007FF71CCA768 ; =============== S U B R O U T I N E =======================================.text:000007FF71CCA768.text:000007FF71CCA768.text:000007FF71CCA768 ; MMRESULT __stdcall timeEndPeriod(UINT uPeriod).text:000007FF71CCA768 public timeEndPeriod.text:000007FF71CCA768 timeEndPeriod proc near ; CODE XREF: timeKillEvent+4D↓p.text:000007FF71CCA768 ; timeSetEventInternal+2AA9↓p ....text:000007FF71CCA768.text:000007FF71CCA768 arg_0 = dword ptr 8.text:000007FF71CCA768 ActualResolution= dword ptr 10h.text:000007FF71CCA768 arg_10 = qword ptr 18h.text:000007FF71CCA768.text:000007FF71CCA768 ; FUNCTION CHUNK AT .text:000007FF71CCD6C0 SIZE 000000CE BYTES.text:000007FF71CCA768.text:000007FF71CCA768 mov [rsp+arg_10], rbx.text:000007FF71CCA76D push rbp.text:000007FF71CCA76E push rsi.text:000007FF71CCA76F push rdi.text:000007FF71CCA770 sub rsp, 20h.text:000007FF71CCA774 mov ebx, ecx.text:000007FF71CCA776 cmp ecx, cs:TDD_MAXRESOLUTION.text:000007FF71CCA77C jb loc_7FF71CCD6C0.text:000007FF71CCA782 cmp ecx, cs:uPeriod.text:000007FF71CCA788 jnb loc_7FF71CCD6CA.text:000007FF71CCA78E lea rcx, ResolutionCritSec ; lpCriticalSection.text:000007FF71CCA795 call cs:__imp_EnterCriticalSection.text:000007FF71CCA79B mov r8d, cs:TDD_MAXRESOLUTION.text:000007FF71CCA7A2 mov r11d, ebx.text:000007FF71CCA7A5 sub r11d, r8d.text:000007FF71CCA7A8 lea r9, TimerData.text:000007FF71CCA7AF xor edi, edi.text:000007FF71CCA7B1 movzx eax, word ptr [r9+r11*2+46h].text:000007FF71CCA7B7 cmp ax, di.text:000007FF71CCA7BA jz loc_7FF71CCD6D1.text:000007FF71CCA7C0 mov ebp, 1.text:000007FF71CCA7C5 sub ax, bp.text:000007FF71CCA7C8 mov [r9+r11*2+46h], ax.text:000007FF71CCA7CE jnz short loc_7FF71CCA82B.text:000007FF71CCA7D0 cmp ebx, cs:dword_7FF71CEE3F8.text:000007FF71CCA7D6 jnz short loc_7FF71CCA82B.text:000007FF71CCA7D8 mov edx, cs:uPeriod.text:000007FF71CCA7DE jmp short $+2.text:000007FF71CCA7E0 ; ---------------------------------------------------------------------------.text:000007FF71CCA7E0.text:000007FF71CCA7E0 loc_7FF71CCA7E0: ; CODE XREF: timeEndPeriod+76↑j.text:000007FF71CCA7E0 ; timeEndPeriod+8B↓j.text:000007FF71CCA7E0 cmp ebx, edx.text:000007FF71CCA7E2 jnb short loc_7FF71CCA7F5.text:000007FF71CCA7E4 mov eax, ebx.text:000007FF71CCA7E6 sub eax, r8d.text:000007FF71CCA7E9 cmp [r9+rax*2+46h], di.text:000007FF71CCA7EF jnz short loc_7FF71CCA7F5.text:000007FF71CCA7F1 add ebx, ebp.text:000007FF71CCA7F3 jmp short loc_7FF71CCA7E0.text:000007FF71CCA7F5 ; ---------------------------------------------------------------------------.text:000007FF71CCA7F5.text:000007FF71CCA7F5 loc_7FF71CCA7F5: ; CODE XREF: timeEndPeriod+7A↑j.text:000007FF71CCA7F5 ; timeEndPeriod+87↑j.text:000007FF71CCA7F5 mov ecx, cs:dword_7FF71CEE3FC.text:000007FF71CCA7FB lea r8, [rsp+38h+ActualResolution] ; ActualResolution.text:000007FF71CCA800 xor edx, edx ; SetOrUnset.text:000007FF71CCA802 imul ecx, 2710h ; RequestedResolution.text:000007FF71CCA808 call cs:__imp_NtSetTimerResolution.text:000007FF71CCA80E mov r11d, cs:uPeriod.text:000007FF71CCA815 mov cs:dword_7FF71CEE3FC, r11d.text:000007FF71CCA81C mov cs:dword_7FF71CEE3F8, ebx.text:000007FF71CCA822 cmp ebx, r11d.text:000007FF71CCA825 jb loc_7FF71CCD6DB.text:000007FF71CCA82B.text:000007FF71CCA82B loc_7FF71CCA82B: ; CODE XREF: timeEndPeriod+66↑j.text:000007FF71CCA82B ; timeEndPeriod+6E↑j ....text:000007FF71CCA82B lea rcx, ResolutionCritSec ; lpCriticalSection.text:000007FF71CCA832 call cs:__imp_LeaveCriticalSection.text:000007FF71CCA838 mov eax, edi.text:000007FF71CCA83A.text:000007FF71CCA83A loc_7FF71CCA83A: ; CODE XREF: timeEndPeriod+2F5D↓j.text:000007FF71CCA83A ; timeEndPeriod+2F64↓j.text:000007FF71CCA83A mov rbx, [rsp+38h+arg_10].text:000007FF71CCA83F add rsp, 20h.text:000007FF71CCA843 pop rdi.text:000007FF71CCA844 pop rsi.text:000007FF71CCA845 pop rbp.text:000007FF71CCA846 retn.text:000007FF71CCA846 ; ---------------------------------------------------------------------------.text:000007FF71CCA847 align 10h.text:000007FF71CCA847 timeEndPeriod endp
--- Quote from: rvk on July 21, 2022, 10:17:27 am ---And what versions of 32 and 64 where you testing for the global issue?
It was also discussed in the other topic that Windows 10 < version 2004 this setting was global, later versions not.
--- End quote ---
All tests, VM and real machine, were done on Win7 SP1 64bit but, I just tested it on Windows 10 21H2 in a VM and on that VM, Sleep remains inaccurate even after running the program. I don't have a Win 10 installation on real hardware (and doubt I'll ever have one.)
Eventually, I'll modify the Sleep test program to call NtSetTimerResolution just before the call to Sleep. Maybe that would make a difference in Win 10.
440bx:
Here is the modified TestSleep program
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$APPTYPE CONSOLE} program TestSleep2; uses Windows, sysutils ; type NTSTATUS = DWORD; const ntdll = 'ntdll'; function NtSetTimerResolution ( { _in_ } InDesiredTime : DWORD; { _in_ } InSetResolution : boolean; { _out_ } OutActualTime : PDWORD ) : NTSTATUS; stdcall; external ntdll; function NtQueryTimerResolution ( { _out_ } OutMinimumResolution : PDWORD; { _out_ } OutMaximumResolution : PDWORD; { _out_ } OutCurrentResolution : PDWORD ) : NTSTATUS; stdcall; external ntdll; const STATUS_OK = 0; var MaximumResolution, MinimumResolution, CurrentResolution : DWORD; Status { NTSTATUS } : DWORD; procedure SleepLoop(ms : DWORD); const COUNTER_LIMIT = 1000; var counter : DWORD = 0; begin writeln; writeln('ms : ', ms, ' repeat count ', COUNTER_LIMIT); for counter := 1 to COUNTER_LIMIT do begin Sleep(ms); end; end; begin writeln; writeln(' NtSetTimerResolution'); writeln; writeln(' sets the timer resolution to maximum resolution possible'); writeln; MinimumResolution := 0; MaximumResolution := 0; CurrentResolution := 0; Status := NtQueryTimerResolution(@MinimumResolution, @MaximumResolution, @CurrentResolution); if Status <> STATUS_OK then begin writeln(' call to NtQueryTimerResolution failed. NTSTATUS: ', IntToHex(Status, 0)); ExitProcess(1); end; writeln; writeln(' Minimum Resolution : ', MinimumResolution); writeln(' Maximum Resolution : ', MaximumResolution); writeln(' Current Resolution : ', CurrentResolution); { for good measure, reset the current resolution variable } CurrentResolution := 0; Status := NtSetTimerResolution(MaximumResolution, TRUE, @CurrentResolution); if Status <> STATUS_OK then begin writeln(' call to NtSetTimerResolution failed. NTSTATUS: ', IntToHex(Status, 0)); ExitProcess(2); end; writeln; writeln(' NtSetTimerResolution returned CurrentResolution : ', CurrentResolution); writeln; { redo the call to NtQueryTimerResolution } MinimumResolution := 0; MaximumResolution := 0; CurrentResolution := 0; Status := NtQueryTimerResolution(@MinimumResolution, @MaximumResolution, @CurrentResolution); writeln(' NtQueryTimerResolution now reports : '); writeln; writeln(' Minimum Resolution : ', MinimumResolution); writeln(' Maximum Resolution : ', MaximumResolution); writeln(' Current Resolution : ', CurrentResolution); SleepLoop(1); { use values 1, 8 and 16 }end. That version, in a Windows 10 21H2 VM executes in a time that is close to the expected time (1s). It usually executes in a hair under 2 seconds while on Win 7 SP1, it executes in 1.1 seconds.
Some of the difference in the timings _might_ be due to Windows defender.
rvk:
--- Quote from: 440bx on July 21, 2022, 11:18:32 am ---
--- Quote from: rvk on July 21, 2022, 10:17:27 am ---Have you tried to put a
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TimeBeginPeriod(1);in your code instead of the NtSetTimerResolution?
--- End quote ---
No, I haven't tried that and, disassembly of that function shows it's just a roundabout way of calling NtSetTimerResolution. The disassembly of that function is:
--- End quote ---
Well, that code/assembler doesn't come from FPC itself.
The TimeBeginPeriod (defined in mmsystem) calls the Windows api timeBeginPeriod in winmm.dll directly.
So at least that one is documented :D (unlike the NtSetTimerResolution).
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---Function timeBeginPeriod(x1: UINT): MMRESULT;stdcall; external 'winmm.dll' name 'timeBeginPeriod';
Navigation
[0] Message Index
[#] Next page