mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +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
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner