Skip to content

Commit 846ad5a

Browse files
authored
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.
1 parent 415cd06 commit 846ad5a

File tree

4 files changed

+110
-138
lines changed

4 files changed

+110
-138
lines changed

Doc/library/time.rst

+24
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ Functions
287287
The reference point of the returned value is undefined, so that only the
288288
difference between the results of two calls is valid.
289289

290+
Clock:
291+
292+
* On Windows, call ``QueryPerformanceCounter()`` and
293+
``QueryPerformanceFrequency()``.
294+
* On macOS, call ``mach_absolute_time()`` and ``mach_timebase_info()``.
295+
* On HP-UX, call ``gethrtime()``.
296+
* Call ``clock_gettime(CLOCK_HIGHRES)`` if available.
297+
* Otherwise, call ``clock_gettime(CLOCK_MONOTONIC)``.
298+
290299
Use :func:`monotonic_ns` to avoid the precision loss caused by the
291300
:class:`float` type.
292301

@@ -316,6 +325,11 @@ Functions
316325
point of the returned value is undefined, so that only the difference between
317326
the results of two calls is valid.
318327

328+
.. impl-detail::
329+
330+
On CPython, use the same clock than :func:`time.monotonic()` and is a
331+
monotonic clock, i.e. a clock that cannot go backwards.
332+
319333
Use :func:`perf_counter_ns` to avoid the precision loss caused by the
320334
:class:`float` type.
321335

@@ -324,6 +338,10 @@ Functions
324338
.. versionchanged:: 3.10
325339
On Windows, the function is now system-wide.
326340

341+
.. versionchanged:: 3.13
342+
Use the same clock than :func:`time.monotonic()`.
343+
344+
327345
.. function:: perf_counter_ns() -> int
328346

329347
Similar to :func:`perf_counter`, but return time as nanoseconds.
@@ -666,6 +684,12 @@ Functions
666684
:class:`struct_time` object is returned, from which the components
667685
of the calendar date may be accessed as attributes.
668686

687+
Clock:
688+
689+
* On Windows, call ``GetSystemTimeAsFileTime()``.
690+
* Call ``clock_gettime(CLOCK_REALTIME)`` if available.
691+
* Otherwise, call ``gettimeofday()``.
692+
669693
Use :func:`time_ns` to avoid the precision loss caused by the :class:`float`
670694
type.
671695

Doc/whatsnew/3.13.rst

+9
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,15 @@ sys
552552
This function is not guaranteed to exist in all implementations of Python.
553553
(Contributed by Serhiy Storchaka in :gh:`78573`.)
554554

555+
time
556+
----
557+
558+
* On Windows, :func:`time.monotonic()` now uses the
559+
``QueryPerformanceCounter()`` clock to have a resolution better than 1 us,
560+
instead of the ``GetTickCount64()`` clock which has a resolution of 15.6 ms.
561+
(Contributed by Victor Stinner in :gh:`88494`.)
562+
563+
555564
tkinter
556565
-------
557566

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
On Windows, :func:`time.monotonic()` now uses the ``QueryPerformanceCounter()``
2+
clock to have a resolution better than 1 us, instead of the
3+
``GetTickCount64()`` clock which has a resolution of 15.6 ms. Patch by Victor
4+
Stinner.

Python/pytime.c

+73-138
Original file line numberDiff line numberDiff line change
@@ -1027,9 +1027,76 @@ _PyTime_TimeWithInfo(PyTime_t *t, _Py_clock_info_t *info)
10271027
}
10281028

10291029

1030+
#ifdef MS_WINDOWS
1031+
static int
1032+
py_win_perf_counter_frequency(_PyTimeFraction *base, int raise_exc)
1033+
{
1034+
LARGE_INTEGER freq;
1035+
// Since Windows XP, the function cannot fail.
1036+
(void)QueryPerformanceFrequency(&freq);
1037+
LONGLONG frequency = freq.QuadPart;
1038+
1039+
// Since Windows XP, frequency cannot be zero.
1040+
assert(frequency >= 1);
1041+
1042+
Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency));
1043+
PyTime_t denom = (PyTime_t)frequency;
1044+
1045+
// Known QueryPerformanceFrequency() values:
1046+
//
1047+
// * 10,000,000 (10 MHz): 100 ns resolution
1048+
// * 3,579,545 Hz (3.6 MHz): 279 ns resolution
1049+
if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) {
1050+
if (raise_exc) {
1051+
PyErr_SetString(PyExc_RuntimeError,
1052+
"invalid QueryPerformanceFrequency");
1053+
}
1054+
return -1;
1055+
}
1056+
return 0;
1057+
}
1058+
1059+
1060+
// N.B. If raise_exc=0, this may be called without the GIL.
1061+
static int
1062+
py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
1063+
{
1064+
assert(info == NULL || raise_exc);
1065+
1066+
static _PyTimeFraction base = {0, 0};
1067+
if (base.denom == 0) {
1068+
if (py_win_perf_counter_frequency(&base, raise_exc) < 0) {
1069+
return -1;
1070+
}
1071+
}
1072+
1073+
if (info) {
1074+
info->implementation = "QueryPerformanceCounter()";
1075+
info->resolution = _PyTimeFraction_Resolution(&base);
1076+
info->monotonic = 1;
1077+
info->adjustable = 0;
1078+
}
1079+
1080+
LARGE_INTEGER now;
1081+
QueryPerformanceCounter(&now);
1082+
LONGLONG ticksll = now.QuadPart;
1083+
1084+
/* Make sure that casting LONGLONG to PyTime_t cannot overflow,
1085+
both types are signed */
1086+
PyTime_t ticks;
1087+
static_assert(sizeof(ticksll) <= sizeof(ticks),
1088+
"LONGLONG is larger than PyTime_t");
1089+
ticks = (PyTime_t)ticksll;
1090+
1091+
*tp = _PyTimeFraction_Mul(ticks, &base);
1092+
return 0;
1093+
}
1094+
#endif // MS_WINDOWS
1095+
1096+
10301097
#ifdef __APPLE__
10311098
static int
1032-
py_mach_timebase_info(_PyTimeFraction *base, int raise)
1099+
py_mach_timebase_info(_PyTimeFraction *base, int raise_exc)
10331100
{
10341101
mach_timebase_info_data_t timebase;
10351102
// According to the Technical Q&A QA1398, mach_timebase_info() cannot
@@ -1051,7 +1118,7 @@ py_mach_timebase_info(_PyTimeFraction *base, int raise)
10511118
// * (1000000000, 33333335) on PowerPC: ~30 ns
10521119
// * (1000000000, 25000000) on PowerPC: 40 ns
10531120
if (_PyTimeFraction_Set(base, numer, denom) < 0) {
1054-
if (raise) {
1121+
if (raise_exc) {
10551122
PyErr_SetString(PyExc_RuntimeError,
10561123
"invalid mach_timebase_info");
10571124
}
@@ -1069,42 +1136,9 @@ py_get_monotonic_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
10691136
assert(info == NULL || raise_exc);
10701137

10711138
#if defined(MS_WINDOWS)
1072-
ULONGLONG ticks = GetTickCount64();
1073-
static_assert(sizeof(ticks) <= sizeof(PyTime_t),
1074-
"ULONGLONG is larger than PyTime_t");
1075-
PyTime_t t;
1076-
if (ticks <= (ULONGLONG)PyTime_MAX) {
1077-
t = (PyTime_t)ticks;
1078-
}
1079-
else {
1080-
// GetTickCount64() maximum is larger than PyTime_t maximum:
1081-
// ULONGLONG is unsigned, whereas PyTime_t is signed.
1082-
t = PyTime_MAX;
1083-
}
1084-
1085-
int res = pytime_mul(&t, MS_TO_NS);
1086-
*tp = t;
1087-
1088-
if (raise_exc && res < 0) {
1089-
pytime_overflow();
1139+
if (py_get_win_perf_counter(tp, info, raise_exc) < 0) {
10901140
return -1;
10911141
}
1092-
1093-
if (info) {
1094-
DWORD timeAdjustment, timeIncrement;
1095-
BOOL isTimeAdjustmentDisabled, ok;
1096-
info->implementation = "GetTickCount64()";
1097-
info->monotonic = 1;
1098-
ok = GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement,
1099-
&isTimeAdjustmentDisabled);
1100-
if (!ok) {
1101-
PyErr_SetFromWindowsErr(0);
1102-
return -1;
1103-
}
1104-
info->resolution = timeIncrement * 1e-7;
1105-
info->adjustable = 0;
1106-
}
1107-
11081142
#elif defined(__APPLE__)
11091143
static _PyTimeFraction base = {0, 0};
11101144
if (base.denom == 0) {
@@ -1190,8 +1224,7 @@ _PyTime_MonotonicUnchecked(void)
11901224
{
11911225
PyTime_t t;
11921226
if (py_get_monotonic_clock(&t, NULL, 0) < 0) {
1193-
// If mach_timebase_info(), clock_gettime() or gethrtime() fails:
1194-
// silently ignore the failure and return 0.
1227+
// Ignore silently the error and return 0.
11951228
t = 0;
11961229
}
11971230
return t;
@@ -1216,122 +1249,24 @@ _PyTime_MonotonicWithInfo(PyTime_t *tp, _Py_clock_info_t *info)
12161249
}
12171250

12181251

1219-
#ifdef MS_WINDOWS
1220-
static int
1221-
py_win_perf_counter_frequency(_PyTimeFraction *base, int raise)
1222-
{
1223-
LONGLONG frequency;
1224-
1225-
LARGE_INTEGER freq;
1226-
// Since Windows XP, the function cannot fail.
1227-
(void)QueryPerformanceFrequency(&freq);
1228-
frequency = freq.QuadPart;
1229-
1230-
// Since Windows XP, frequency cannot be zero.
1231-
assert(frequency >= 1);
1232-
1233-
Py_BUILD_ASSERT(sizeof(PyTime_t) == sizeof(frequency));
1234-
PyTime_t denom = (PyTime_t)frequency;
1235-
1236-
// Known QueryPerformanceFrequency() values:
1237-
//
1238-
// * 10,000,000 (10 MHz): 100 ns resolution
1239-
// * 3,579,545 Hz (3.6 MHz): 279 ns resolution
1240-
if (_PyTimeFraction_Set(base, SEC_TO_NS, denom) < 0) {
1241-
if (raise) {
1242-
PyErr_SetString(PyExc_RuntimeError,
1243-
"invalid QueryPerformanceFrequency");
1244-
}
1245-
return -1;
1246-
}
1247-
return 0;
1248-
}
1249-
1250-
1251-
// N.B. If raise_exc=0, this may be called without the GIL.
1252-
static int
1253-
py_get_win_perf_counter(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc)
1254-
{
1255-
assert(info == NULL || raise_exc);
1256-
1257-
static _PyTimeFraction base = {0, 0};
1258-
if (base.denom == 0) {
1259-
if (py_win_perf_counter_frequency(&base, raise_exc) < 0) {
1260-
return -1;
1261-
}
1262-
}
1263-
1264-
if (info) {
1265-
info->implementation = "QueryPerformanceCounter()";
1266-
info->resolution = _PyTimeFraction_Resolution(&base);
1267-
info->monotonic = 1;
1268-
info->adjustable = 0;
1269-
}
1270-
1271-
LARGE_INTEGER now;
1272-
QueryPerformanceCounter(&now);
1273-
LONGLONG ticksll = now.QuadPart;
1274-
1275-
/* Make sure that casting LONGLONG to PyTime_t cannot overflow,
1276-
both types are signed */
1277-
PyTime_t ticks;
1278-
static_assert(sizeof(ticksll) <= sizeof(ticks),
1279-
"LONGLONG is larger than PyTime_t");
1280-
ticks = (PyTime_t)ticksll;
1281-
1282-
PyTime_t ns = _PyTimeFraction_Mul(ticks, &base);
1283-
*tp = ns;
1284-
return 0;
1285-
}
1286-
#endif // MS_WINDOWS
1287-
1288-
12891252
int
12901253
_PyTime_PerfCounterWithInfo(PyTime_t *t, _Py_clock_info_t *info)
12911254
{
1292-
#ifdef MS_WINDOWS
1293-
return py_get_win_perf_counter(t, info, 1);
1294-
#else
12951255
return _PyTime_MonotonicWithInfo(t, info);
1296-
#endif
12971256
}
12981257

12991258

13001259
PyTime_t
13011260
_PyTime_PerfCounterUnchecked(void)
13021261
{
1303-
PyTime_t t;
1304-
int res;
1305-
#ifdef MS_WINDOWS
1306-
res = py_get_win_perf_counter(&t, NULL, 0);
1307-
#else
1308-
res = py_get_monotonic_clock(&t, NULL, 0);
1309-
#endif
1310-
if (res < 0) {
1311-
// If py_win_perf_counter_frequency() or py_get_monotonic_clock()
1312-
// fails: silently ignore the failure and return 0.
1313-
t = 0;
1314-
}
1315-
return t;
1262+
return _PyTime_MonotonicUnchecked();
13161263
}
13171264

13181265

13191266
int
13201267
PyTime_PerfCounter(PyTime_t *result)
13211268
{
1322-
int res;
1323-
#ifdef MS_WINDOWS
1324-
res = py_get_win_perf_counter(result, NULL, 1);
1325-
#else
1326-
res = py_get_monotonic_clock(result, NULL, 1);
1327-
#endif
1328-
if (res < 0) {
1329-
// If py_win_perf_counter_frequency() or py_get_monotonic_clock()
1330-
// fails: silently ignore the failure and return 0.
1331-
*result = 0;
1332-
return -1;
1333-
}
1334-
return 0;
1269+
return PyTime_Monotonic(result);
13351270
}
13361271

13371272

0 commit comments

Comments
 (0)