mirror of
https://github.com/python/cpython
synced 2024-09-05 00:05:39 +00:00
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:
parent
415cd06d72
commit
846ad5a26a
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
-------
|
||||
|
||||
|
|
|
@ -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.
|
211
Python/pytime.c
211
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue