Frankly, I didn't like it, either.  But there doesn't seem to be a good way to dynamically determine the Windows version.  And a compile time option would need to default to the least common denominator of not having the high res timer available.  But there's really no point in using this code for Windows versions prior to 2004, anyway.

So see if you like the attached version any better.  I don't know how Windows versions prior to 2004 will work, but probably the change to see if the timer can be created is sufficient, and retry with a different parameter if it fails.  BTW, this change doesn't change the timing results.  It turns out that a registry query takes microseconds, apparently.

On 1/2/2022 3:50 PM, Óscar Fuentes wrote:
Glenn Burkhardt <gbburkha...@gmail.com>
writes:

Reading the registry on every call to clock_nanosleep is wasteful.



_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public
*** clock.c.orig        Sun Jan  2 14:09:32 2022
--- clock.c     Sun Jan  2 17:52:49 2022
***************
*** 6,11 ****
--- 6,12 ----
  
  #include <errno.h>
  #include <stdint.h>
+ #include <stdbool.h>
  #include <time.h>
  #include <windows.h>
  #ifndef IN_WINPTHREAD
***************
*** 213,221 ****
      return lc_set_errno(EINVAL);
  }
  
  /**
   * Sleep for the specified time.
!  * @param  clock_id This argument should always be CLOCK_REALTIME (0).
   * @param  flags 0 for relative sleep interval, others for absolute waking up.
   * @param  request The desired sleep interval or absolute waking up time.
   * @param  remain The remain amount of time to sleep.
--- 214,239 ----
      return lc_set_errno(EINVAL);
  }
  
+ // 
https://randomascii.wordpress.com/2020/10/04/windows-timer-resolution-the-great-rule-change/
+ // Feature available in Windows 2004 and later
+ #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
+ 
+ static inline
+ void timersub(struct timespec* c, 
+               const struct timespec* a,
+               const struct timespec* b)
+ {
+     c->tv_sec = a->tv_sec - b->tv_sec;
+     c->tv_nsec = a->tv_nsec - b->tv_nsec;
+     if (c->tv_nsec < 0) {
+         c->tv_sec--;
+         c->tv_nsec += POW10_9;
+     }
+ }
+ 
  /**
   * Sleep for the specified time.
!  * @param  clock_id: CLOCK_REALTIME or CLOCK_MONOTONIC
   * @param  flags 0 for relative sleep interval, others for absolute waking up.
   * @param  request The desired sleep interval or absolute waking up time.
   * @param  remain The remain amount of time to sleep.
***************
*** 225,252 ****
   *         with errno set to indicate the error.
   */
  int clock_nanosleep(clockid_t clock_id, int flags,
!                            const struct timespec *request,
!                            struct timespec *remain)
  {
      struct timespec tp;
  
!     if (clock_id != CLOCK_REALTIME)
!         return lc_set_errno(EINVAL);
  
!     if (flags == 0)
!         return nanosleep(request, remain);
  
!     /* TIMER_ABSTIME = 1 */
!     clock_gettime(CLOCK_REALTIME, &tp);
  
!     tp.tv_sec = request->tv_sec - tp.tv_sec;
!     tp.tv_nsec = request->tv_nsec - tp.tv_nsec;
!     if (tp.tv_nsec < 0) {
!         tp.tv_nsec += POW10_9;
!         tp.tv_sec --;
!     }
  
!     return nanosleep(&tp, remain);
  }
  
  /**
--- 243,330 ----
   *         with errno set to indicate the error.
   */
  int clock_nanosleep(clockid_t clock_id, int flags,
!                     const struct timespec *request,
!                     struct timespec *remain)
  {
      struct timespec tp;
  
!     switch (clock_id) {
!       case CLOCK_REALTIME:
  
!         /* TIMER_ABSTIME = 1 */
!         if (flags == 0)
!             return nanosleep(request, remain);
! 
!         // sleep until abs time
!         clock_gettime(CLOCK_REALTIME, &tp);
! 
!         tp.tv_sec = request->tv_sec - tp.tv_sec;
!         tp.tv_nsec = request->tv_nsec - tp.tv_nsec;
!         if (tp.tv_nsec < 0) {
!             tp.tv_nsec += POW10_9;
!             tp.tv_sec --;
!         }
  
!         return nanosleep(&tp, remain);
!     
!       case CLOCK_MONOTONIC:
!         if (request->tv_sec < 0 || request->tv_nsec < 0 || request->tv_nsec 
>= POW10_9) {
!             return lc_set_errno(EINVAL);
!         }
!         // 
https://docs.microsoft.com/en-us/windows/win32/sync/using-waitable-timer-objects
  
!         // 100ns intervals, Positive values indicate absolute time.
!         LARGE_INTEGER liDueTime;
!         liDueTime.QuadPart = ((long long)request->tv_sec*POW10_9 + 
request->tv_nsec)/100;
!         if (flags == 0) {
!             liDueTime.QuadPart = -liDueTime.QuadPart;
!         } else {
!             liDueTime.QuadPart += DELTA_EPOCH_IN_100NS; // TIMER_ABSTIME
!         }
  
!         // Create an unnamed waitable timer.  Try first for high res timer.
!         HANDLE hTimer = CreateWaitableTimerEx(0, 0, 
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
!         if (!hTimer) {
!             // Try for low res timer
!             hTimer = CreateWaitableTimerEx(0, 0, 
CREATE_WAITABLE_TIMER_MANUAL_RESET, TIMER_ALL_ACCESS);
!             if (!hTimer) {
!                 return lc_set_errno(ENOTSUP);
!             }
!         }
!     
!         struct timespec then;
!         if (remain) clock_gettime(CLOCK_MONOTONIC, &then);
! 
!         // Set a timer
!         int retval = 0;
!         if (!SetWaitableTimer(hTimer, &liDueTime, 0, 0, 0, 0)) {
!             retval = lc_set_errno(ENOTSUP);
!         } else {
!             // Wait for the timer.
!             DWORD st = WaitForSingleObject(hTimer, INFINITE);
!             if (st != WAIT_OBJECT_0) {
!                 retval = lc_set_errno(ENOTSUP);
!             }
!         }
!         
!         CloseHandle(hTimer);
! 
!         struct timespec now, elapsed;
!         if (remain) {
!             clock_gettime(CLOCK_MONOTONIC, &now);
!             timersub(&elapsed, &now, &then);
!             timersub(remain, request, &elapsed);
!             if (remain->tv_sec < 0) {
!                 remain->tv_sec = 0;
!                 remain->tv_nsec = 0;
!             }
!         }
! 
!         return retval;
! 
!       default:
!         return lc_set_errno(EINVAL);
!     }
  }
  
  /**
***************
*** 278,280 ****
--- 356,485 ----
  
      return 0;
  }
+ 
+ #ifdef UNIT_TEST
+ // gcc -DUNIT_TEST -g -O2 clock_nanosleep.c -o n -Wall -lwinmm
+ 
+ #include <stdio.h>
+ 
+ typedef NTSTATUS NTAPI (*NtQueryTimerResolution)(PULONG MinimumResolution,
+                                                  PULONG MaximumResolution,
+                                                  PULONG CurrentResolution);
+ 
+ // Returns the current timer resolution in 100 ns units (10,000 implies
+ // a one ms timer interval).
+ ULONG GetTimerResolution() {
+   HMODULE ntdll = LoadLibraryA("ntdll.dll");
+   NtQueryTimerResolution QueryTimerResolution
+      = (NtQueryTimerResolution)GetProcAddress(ntdll, 
"NtQueryTimerResolution");
+ 
+   ULONG minimum, maximum, current;
+   QueryTimerResolution(&minimum, &maximum, &current);
+   return current;
+ }
+ 
+ static inline void timeradd(struct timespec* c, 
+                             const struct timespec* a,
+                             const struct timespec* b)
+ {
+     c->tv_sec = a->tv_sec + b->tv_sec;
+     c->tv_nsec = a->tv_nsec + b->tv_nsec;
+     if (c->tv_nsec >= POW10_9) {
+         c->tv_sec++;
+         c->tv_nsec -= POW10_9;
+     }
+ }
+ 
+ // Windows 2004, 
https://docs.microsoft.com/en-us/windows/release-health/release-information
+ #define Win2004_BUILD_NUMBER 19041  
+ 
+ static inline
+ bool haveHighResTimer()
+ {
+     char bn[32];
+     DWORD sz = sizeof(bn);
+ 
+     // Determine whether Windows version is at least Win 2004
+     int s = RegGetValueA(HKEY_LOCAL_MACHINE, 
+                          "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 
+                          "CurrentBuildNumber", RRF_RT_REG_SZ, 0, bn, &sz);
+     if (!s) {
+         int curVer = atoi(bn);
+         return (curVer >= Win2004_BUILD_NUMBER);
+     }
+     return false;
+ }    
+ 
+ int main(int ac, char**av)
+ {
+     int iterations = 10;
+     struct timespec then, now, elapsed, remain;
+     struct timespec delayTime = { .tv_sec=0, .tv_nsec=1000000 };
+ 
+     if (ac > 1) delayTime.tv_nsec = atoi(av[1]);
+     if (ac > 2) iterations = atoi(av[2]);
+     
+     if (haveHighResTimer())
+         printf("High Res timer\n");
+     else
+         printf("Old windows version: no high res timer\n");
+ 
+     ULONG ns100 = GetTimerResolution();
+     double timerRes = ns100/10000.;
+     printf("Timer resolution: %.6f ms\n", timerRes);
+ 
+     TIMECAPS caps;    // use -lwinmm
+     int s = timeGetDevCaps(&caps, sizeof(caps));
+     if (s != TIMERR_NOERROR) perror("timeGetDevCaps");
+ 
+     printf("caps: min %d ms, max %d ms\n", caps.wPeriodMin, caps.wPeriodMax);
+ 
+     if (caps.wPeriodMin > timerRes) {
+         printf("No reason to set min timer resolution\n");
+     } else {
+         timeBeginPeriod(caps.wPeriodMin);
+         printf("Timer res set to minimum\n");
+     }
+     
+     clock_gettime(CLOCK_MONOTONIC, &then);
+ 
+     for (int i=0; i < iterations; i++) {
+         int s = clock_nanosleep(CLOCK_MONOTONIC, 0, &delayTime, &remain);
+         if (s) { perror("clock_nanosleep"); break; }
+     }
+ 
+     clock_gettime(CLOCK_MONOTONIC, &now);
+ 
+     timersub(&elapsed, &now, &then);
+ 
+     printf("remain %jd sec, %ld ns\n", remain.tv_sec, remain.tv_nsec);
+     printf("elapsed  %.7f sec\n", elapsed.tv_sec + 
elapsed.tv_nsec*1./POW10_9);
+ 
+     double dt = delayTime.tv_nsec*1./POW10_9;
+     printf("expected %.7f sec, %d iterations, %.7f sec delay\n", 
+            iterations*dt, iterations, dt);
+ 
+     // wait until absolute time
+     long long waitTime = delayTime.tv_sec * POW10_9 + delayTime.tv_nsec;
+     waitTime *= iterations;
+     delayTime.tv_sec = waitTime/(double)POW10_9;
+     delayTime.tv_nsec = waitTime - delayTime.tv_sec*(long long)POW10_9;
+     
+     clock_gettime(CLOCK_MONOTONIC, &then);
+ 
+     struct timespec until;
+     timeradd(&until, &then, &delayTime);
+ 
+     s = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &delayTime, &remain);
+     if (s) perror("clock_nanosleep");
+     
+     clock_gettime(CLOCK_MONOTONIC, &now);
+ 
+     printf("\n wait until abstime\n");
+     printf("now:      %jd sec %ld nsec\n", now.tv_sec, now.tv_nsec);
+     printf("expected: %jd sec %ld nsec\n", until.tv_sec, until.tv_nsec);
+     printf("remain    %jd sec, %ld ns\n", remain.tv_sec, remain.tv_nsec);
+     
+     return 0;
+ }
+ #endif
_______________________________________________
Mingw-w64-public mailing list
Mingw-w64-public@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mingw-w64-public

Reply via email to