mirror of
https://github.com/python/cpython.git
synced 2025-07-19 01:05:26 +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
4 changed files with 110 additions and 138 deletions
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…
Add table
Add a link
Reference in a new issue