Forum > Windows

Windows Sleep precision

(1/2) > >>

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

Go to full version