Skip to content

Commit

Permalink
pythongh-88494: Use QueryPerformanceCounter() for time.monotonic()
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vstinner committed Mar 14, 2024
1 parent 3a25d9c commit 84632e9
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 138 deletions.
17 changes: 17 additions & 0 deletions Doc/library/time.rst
Expand Up @@ -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.

Expand Down Expand Up @@ -316,6 +325,8 @@ Functions
point of the returned value is undefined, so that only the difference between
the results of two calls is valid.

Use the same clock than :func:`time.monotonic()`.

Use :func:`perf_counter_ns` to avoid the precision loss caused by the
:class:`float` type.

Expand Down Expand Up @@ -666,6 +677,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.

Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -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
-------

Expand Down
@@ -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 changes: 73 additions & 138 deletions Python/pytime.c
Expand Up @@ -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
Expand All @@ -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");
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}


Expand Down

0 comments on commit 84632e9

Please sign in to comment.