summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2024-03-14 15:42:41 (GMT)
committerGitHub <noreply@github.com>2024-03-14 15:42:41 (GMT)
commit846ad5a26ac0ff988a3fceec8f8e830f68bdf48a (patch)
tree59187e5155d80521daf817214a4b80bb139c23df
parent415cd06d724762f23b42f1ab36867b8114714684 (diff)
downloadcpython-846ad5a26ac0ff988a3fceec8f8e830f68bdf48a.zip
cpython-846ad5a26ac0ff988a3fceec8f8e830f68bdf48a.tar.gz
cpython-846ad5a26ac0ff988a3fceec8f8e830f68bdf48a.tar.bz2
gh-88494: Use QueryPerformanceCounter() for time.monotonic() (#116781)
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.
-rw-r--r--Doc/library/time.rst24
-rw-r--r--Doc/whatsnew/3.13.rst9
-rw-r--r--Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst4
-rw-r--r--Python/pytime.c211
4 files changed, 110 insertions, 138 deletions
diff --git a/Doc/library/time.rst b/Doc/library/time.rst
index 029663e..d79ca6e 100644
--- a/Doc/library/time.rst
+++ b/Doc/library/time.rst
@@ -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.
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index aec0295..ea45fa7 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -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
index 0000000..5a96af0
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2024-03-14-09-14-21.gh-issue-88494.Bwfmp7.rst
@@ -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.
diff --git a/Python/pytime.c b/Python/pytime.c
index 70d92ca..45be6a3 100644
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -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);
}