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

View 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
-------

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__
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);
}