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.
This commit is contained in:
Victor Stinner 2024-03-14 16:42:41 +01:00 committed by GitHub
parent 415cd06d72
commit 846ad5a26a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 110 additions and 138 deletions

View File

@ -287,6 +287,15 @@ Functions
The reference point of the returned value is undefined, so that only the The reference point of the returned value is undefined, so that only the
difference between the results of two calls is valid. 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 Use :func:`monotonic_ns` to avoid the precision loss caused by the
:class:`float` type. :class:`float` type.
@ -316,6 +325,11 @@ Functions
point of the returned value is undefined, so that only the difference between point of the returned value is undefined, so that only the difference between
the results of two calls is valid. 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 Use :func:`perf_counter_ns` to avoid the precision loss caused by the
:class:`float` type. :class:`float` type.
@ -324,6 +338,10 @@ Functions
.. versionchanged:: 3.10 .. versionchanged:: 3.10
On Windows, the function is now system-wide. On Windows, the function is now system-wide.
.. versionchanged:: 3.13
Use the same clock than :func:`time.monotonic()`.
.. function:: perf_counter_ns() -> int .. function:: perf_counter_ns() -> int
Similar to :func:`perf_counter`, but return time as nanoseconds. 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 :class:`struct_time` object is returned, from which the components
of the calendar date may be accessed as attributes. 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` Use :func:`time_ns` to avoid the precision loss caused by the :class:`float`
type. type.

View File

@ -552,6 +552,15 @@ sys
This function is not guaranteed to exist in all implementations of Python. This function is not guaranteed to exist in all implementations of Python.
(Contributed by Serhiy Storchaka in :gh:`78573`.) (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 tkinter
------- -------

View File

@ -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.

View 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__ #ifdef __APPLE__
static int 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; mach_timebase_info_data_t timebase;
// According to the Technical Q&A QA1398, mach_timebase_info() cannot // 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, 33333335) on PowerPC: ~30 ns
// * (1000000000, 25000000) on PowerPC: 40 ns // * (1000000000, 25000000) on PowerPC: 40 ns
if (_PyTimeFraction_Set(base, numer, denom) < 0) { if (_PyTimeFraction_Set(base, numer, denom) < 0) {
if (raise) { if (raise_exc) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RuntimeError,
"invalid mach_timebase_info"); "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); assert(info == NULL || raise_exc);
#if defined(MS_WINDOWS) #if defined(MS_WINDOWS)
ULONGLONG ticks = GetTickCount64(); if (py_get_win_perf_counter(tp, info, raise_exc) < 0) {
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();
return -1; 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__) #elif defined(__APPLE__)
static _PyTimeFraction base = {0, 0}; static _PyTimeFraction base = {0, 0};
if (base.denom == 0) { if (base.denom == 0) {
@ -1190,8 +1224,7 @@ _PyTime_MonotonicUnchecked(void)
{ {
PyTime_t t; PyTime_t t;
if (py_get_monotonic_clock(&t, NULL, 0) < 0) { if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
// If mach_timebase_info(), clock_gettime() or gethrtime() fails: // Ignore silently the error and return 0.
// silently ignore the failure and return 0.
t = 0; t = 0;
} }
return t; 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 int
_PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info) _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); return _PyTime_MonotonicWithInfo(t, info);
#endif
} }
PyTime_t PyTime_t
_PyTime_PerfCounterUnchecked(void) _PyTime_PerfCounterUnchecked(void)
{ {
PyTime_t t; return _PyTime_MonotonicUnchecked();
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;
} }
int int
PyTime_PerfCounter(PyTime_t *result) PyTime_PerfCounter(PyTime_t *result)
{ {
int res; return PyTime_Monotonic(result);
#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;
} }