mirror of https://github.com/python/cpython
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
|
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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -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__
|
#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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue