]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-88494: Use QueryPerformanceCounter() for time.monotonic() (#116781)
authorVictor Stinner <vstinner@python.org>
Thu, 14 Mar 2024 15:42:41 +0000 (16:42 +0100)
committerGitHub <noreply@github.com>
Thu, 14 Mar 2024 15:42:41 +0000 (16:42 +0100)
On Windows, time.monotonic() now uses the QueryPerformanceCounter()
clock to have a resolution better than 1 us, instead of the
gGetTickCount64() clock which has a resolution of 15.6 ms.

Doc/library/time.rst
Doc/whatsnew/3.13.rst
Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst [new file with mode: 0644]
Python/pytime.c

index 029663e0801a0dc80b136827daf6ed73adf0a7d2..d79ca6e12081070c7ab5fd847059578181e0e9dd 100644 (file)
@@ -287,6 +287,15 @@ Functions
    The reference point of the returned value is undefined, so that only the
    difference between the results of two calls is valid.
 
+   Clock:
+
+   * On Windows, call ``QueryPerformanceCounter()`` and
+     ``QueryPerformanceFrequency()``.
+   * On macOS, call ``mach_absolute_time()`` and ``mach_timebase_info()``.
+   * On HP-UX, call ``gethrtime()``.
+   * Call ``clock_gettime(CLOCK_HIGHRES)`` if available.
+   * Otherwise, call ``clock_gettime(CLOCK_MONOTONIC)``.
+
    Use :func:`monotonic_ns` to avoid the precision loss caused by the
    :class:`float` type.
 
@@ -316,6 +325,11 @@ Functions
    point of the returned value is undefined, so that only the difference between
    the results of two calls is valid.
 
+   .. impl-detail::
+
+      On CPython, use the same clock than :func:`time.monotonic()` and is a
+      monotonic clock, i.e. a clock that cannot go backwards.
+
    Use :func:`perf_counter_ns` to avoid the precision loss caused by the
    :class:`float` type.
 
@@ -324,6 +338,10 @@ Functions
    .. versionchanged:: 3.10
       On Windows, the function is now system-wide.
 
+   .. versionchanged:: 3.13
+      Use the same clock than :func:`time.monotonic()`.
+
+
 .. function:: perf_counter_ns() -> int
 
    Similar to :func:`perf_counter`, but return time as nanoseconds.
@@ -666,6 +684,12 @@ Functions
    :class:`struct_time` object is returned, from which the components
    of the calendar date may be accessed as attributes.
 
+   Clock:
+
+   * On Windows, call ``GetSystemTimeAsFileTime()``.
+   * Call ``clock_gettime(CLOCK_REALTIME)`` if available.
+   * Otherwise, call ``gettimeofday()``.
+
    Use :func:`time_ns` to avoid the precision loss caused by the :class:`float`
    type.
 
index aec02954b9fea9817e4d8e3e5806f83f329f068f..ea45fa7c825dfeacf7ebe3374c64d57328292363 100644 (file)
@@ -552,6 +552,15 @@ sys
   This function is not guaranteed to exist in all implementations of Python.
   (Contributed by Serhiy Storchaka in :gh:`78573`.)
 
+time
+----
+
+* On Windows, :func:`time.monotonic()` now uses the
+  ``QueryPerformanceCounter()`` clock to have a resolution better than 1 us,
+  instead of the ``GetTickCount64()`` clock which has a resolution of 15.6 ms.
+  (Contributed by Victor Stinner in :gh:`88494`.)
+
+
 tkinter
 -------
 
diff --git a/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst b/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst
new file mode 100644 (file)
index 0000000..5a96af0
--- /dev/null
@@ -0,0 +1,4 @@
+On Windows, :func:`time.monotonic()` now uses the ``QueryPerformanceCounter()``
+clock to have a resolution better than 1 us, instead of the
+``GetTickCount64()`` clock which has a resolution of 15.6 ms. Patch by Victor
+Stinner.
index 70d92ca00ee28e3f72e825c9490fc3dcb88bca80..45be6a3dbd334133aea786e68566afa43990889e 100644 (file)
@@ -1027,9 +1027,76 @@ _PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info)
 }
 
 
+#ifdef MS_WINDOWS
+static int
+py_win_perf_counter_frequency(_PyTimeFraction *base, int raise_exc)
+{
+    LARGE_INTEGER freq;
+    // Since Windows XP, the function cannot fail.
+    (void)QueryPerformanceFrequency(&freq);
+    LONGLONG frequency = freq.QuadPart;
+
+    // Since Windows XP, frequency cannot be zero.
+    assert(frequency >= 1);
+
+    Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency));
+    PyTime_t denom = (PyTime_t)frequency;
+
+    // Known QueryPerformanceFrequency() values:
+    //
+    // * 10,000,000 (10 MHz): 100 ns resolution
+    // * 3,579,545 Hz (3.6 MHz): 279 ns resolution
+    if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) {
+        if (raise_exc) {
+            PyErr_SetString(PyExc_RuntimeError,
+                            "invalid QueryPerformanceFrequency");
+        }
+        return -1;
+    }
+    return 0;
+}
+
+
+// N.B. If raise_exc=0, this may be called without the GIL.
+static int
+py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
+{
+    assert(info == NULL || raise_exc);
+
+    static _PyTimeFraction base = {0, 0};
+    if (base.denom == 0) {
+        if (py_win_perf_counter_frequency(&base, raise_exc) < 0) {
+            return -1;
+        }
+    }
+
+    if (info) {
+        info->implementation = "QueryPerformanceCounter()";
+        info->resolution = _PyTimeFraction_Resolution(&base);
+        info->monotonic = 1;
+        info->adjustable = 0;
+    }
+
+    LARGE_INTEGER now;
+    QueryPerformanceCounter(&now);
+    LONGLONG ticksll = now.QuadPart;
+
+    /* Make sure that casting LONGLONG to PyTime_t cannot overflow,
+       both types are signed */
+    PyTime_t ticks;
+    static_assert(sizeof(ticksll) <= sizeof(ticks),
+                  "LONGLONG is larger than PyTime_t");
+    ticks = (PyTime_t)ticksll;
+
+    *tp = _PyTimeFraction_Mul(ticks, &base);
+    return 0;
+}
+#endif  // MS_WINDOWS
+
+
 #ifdef __APPLE__
 static int
-py_mach_timebase_info(_PyTimeFraction *base, int raise)
+py_mach_timebase_info(_PyTimeFraction *base, int raise_exc)
 {
     mach_timebase_info_data_t timebase;
     // According to the Technical Q&A QA1398, mach_timebase_info() cannot
@@ -1051,7 +1118,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise)
     // * (1000000000, 33333335) on PowerPC: ~30 ns
     // * (1000000000, 25000000) on PowerPC: 40 ns
     if (_PyTimeFraction_Set(base, numer, denom) < 0) {
-        if (raise) {
+        if (raise_exc) {
             PyErr_SetString(PyExc_RuntimeError,
                             "invalid mach_timebase_info");
         }
@@ -1069,42 +1136,9 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
     assert(info == NULL || raise_exc);
 
 #if defined(MS_WINDOWS)
-    ULONGLONG ticks = GetTickCount64();
-    static_assert(sizeof(ticks) <= sizeof(PyTime_t),
-                  "ULONGLONG is larger than PyTime_t");
-    PyTime_t t;
-    if (ticks <= (ULONGLONG)PyTime_MAX) {
-        t = (PyTime_t)ticks;
-    }
-    else {
-        // GetTickCount64() maximum is larger than PyTime_t maximum:
-        // ULONGLONG is unsigned, whereas PyTime_t is signed.
-        t = PyTime_MAX;
-    }
-
-    int res = pytime_mul(&t, MS_TO_NS);
-    *tp = t;
-
-    if (raise_exc && res < 0) {
-        pytime_overflow();
+    if (py_get_win_perf_counter(tp, info, raise_exc) < 0) {
         return -1;
     }
-
-    if (info) {
-        DWORD timeAdjustment, timeIncrement;
-        BOOL isTimeAdjustmentDisabled, ok;
-        info->implementation = "GetTickCount64()";
-        info->monotonic = 1;
-        ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement,
-                                     &isTimeAdjustmentDisabled);
-        if (!ok) {
-            PyErr_SetFromWindowsErr(0);
-            return -1;
-        }
-        info->resolution = timeIncrement * 1e-7;
-        info->adjustable = 0;
-    }
-
 #elif defined(__APPLE__)
     static _PyTimeFraction base = {0, 0};
     if (base.denom == 0) {
@@ -1190,8 +1224,7 @@ _PyTime_MonotonicUnchecked(void)
 {
     PyTime_t t;
     if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
-        // If mach_timebase_info(), clock_gettime() or gethrtime() fails:
-        // silently ignore the failure and return 0.
+        // Ignore silently the error and return 0.
         t = 0;
     }
     return t;
@@ -1216,122 +1249,24 @@ _PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
 }
 
 
-#ifdef MS_WINDOWS
-static int
-py_win_perf_counter_frequency(_PyTimeFraction *base, int raise)
-{
-    LONGLONG frequency;
-
-    LARGE_INTEGER freq;
-    // Since Windows XP, the function cannot fail.
-    (void)QueryPerformanceFrequency(&freq);
-    frequency = freq.QuadPart;
-
-    // Since Windows XP, frequency cannot be zero.
-    assert(frequency >= 1);
-
-    Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency));
-    PyTime_t denom = (PyTime_t)frequency;
-
-    // Known QueryPerformanceFrequency() values:
-    //
-    // * 10,000,000 (10 MHz): 100 ns resolution
-    // * 3,579,545 Hz (3.6 MHz): 279 ns resolution
-    if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) {
-        if (raise) {
-            PyErr_SetString(PyExc_RuntimeError,
-                            "invalid QueryPerformanceFrequency");
-        }
-        return -1;
-    }
-    return 0;
-}
-
-
-// N.B. If raise_exc=0, this may be called without the GIL.
-static int
-py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
-{
-    assert(info == NULL || raise_exc);
-
-    static _PyTimeFraction base = {0, 0};
-    if (base.denom == 0) {
-        if (py_win_perf_counter_frequency(&base, raise_exc) < 0) {
-            return -1;
-        }
-    }
-
-    if (info) {
-        info->implementation = "QueryPerformanceCounter()";
-        info->resolution = _PyTimeFraction_Resolution(&base);
-        info->monotonic = 1;
-        info->adjustable = 0;
-    }
-
-    LARGE_INTEGER now;
-    QueryPerformanceCounter(&now);
-    LONGLONG ticksll = now.QuadPart;
-
-    /* Make sure that casting LONGLONG to PyTime_t cannot overflow,
-       both types are signed */
-    PyTime_t ticks;
-    static_assert(sizeof(ticksll) <= sizeof(ticks),
-                  "LONGLONG is larger than PyTime_t");
-    ticks = (PyTime_t)ticksll;
-
-    PyTime_t ns = _PyTimeFraction_Mul(ticks, &base);
-    *tp = ns;
-    return 0;
-}
-#endif  // MS_WINDOWS
-
-
 int
 _PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info)
 {
-#ifdef MS_WINDOWS
-    return py_get_win_perf_counter(t, info, 1);
-#else
     return _PyTime_MonotonicWithInfo(t, info);
-#endif
 }
 
 
 PyTime_t
 _PyTime_PerfCounterUnchecked(void)
 {
-    PyTime_t t;
-    int res;
-#ifdef MS_WINDOWS
-    res = py_get_win_perf_counter(&t, NULL, 0);
-#else
-    res = py_get_monotonic_clock(&t, NULL, 0);
-#endif
-    if (res  < 0) {
-        // If py_win_perf_counter_frequency() or py_get_monotonic_clock()
-        // fails: silently ignore the failure and return 0.
-        t = 0;
-    }
-    return t;
+    return _PyTime_MonotonicUnchecked();
 }
 
 
 int
 PyTime_PerfCounter(PyTime_t *result)
 {
-    int res;
-#ifdef MS_WINDOWS
-    res = py_get_win_perf_counter(result, NULL, 1);
-#else
-    res = py_get_monotonic_clock(result, NULL, 1);
-#endif
-    if (res  < 0) {
-        // If py_win_perf_counter_frequency() or py_get_monotonic_clock()
-        // fails: silently ignore the failure and return 0.
-        *result = 0;
-        return -1;
-    }
-    return 0;
+    return PyTime_Monotonic(result);
 }