-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
time.perf_counter(): compute Greatest Common Denonimator (GCD) to reduce risk of integer overflow #112567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _Py_GetTicksPerSecond() * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * Remove _PyTime_Init(). * Remove _PyRuntimeState.time member. * os.times() now calls _Py_GetTicksPerSecond(). * Rename _PyTime_GetClockWithInfo() to py_clock().
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _Py_GetTicksPerSecond() * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * Remove _PyTime_Init(). * Remove _PyRuntimeState.time member. * os.times() now calls _Py_GetTicksPerSecond(). * Rename _PyTime_GetClockWithInfo() to py_clock().
QueryPerformanceCounter() * (SEC_TO_SEC / QueryPerformanceFrequency()) +
QueryPerformanceCounter() * (SEC_TO_SEC % QueryPerformanceFrequency()) / QueryPerformanceFrequency() or SEC_TO_SEC * (QueryPerformanceCounter() / QueryPerformanceFrequency()) +
SEC_TO_SEC * (QueryPerformanceCounter() % QueryPerformanceFrequency()) / QueryPerformanceFrequency() or QueryPerformanceCounter() * (SEC_TO_SEC / QueryPerformanceFrequency()) +
(SEC_TO_SEC % QueryPerformanceFrequency()) * (QueryPerformanceCounter() / QueryPerformanceFrequency()) +
(SEC_TO_SEC % QueryPerformanceFrequency()) * (QueryPerformanceCounter() % QueryPerformanceFrequency()) / QueryPerformanceFrequency() or SEC_TO_SEC * (QueryPerformanceCounter() / QueryPerformanceFrequency()) +
(QueryPerformanceCounter() % QueryPerformanceFrequency()) * (SEC_TO_SEC / QueryPerformanceFrequency()) +
(QueryPerformanceCounter() % QueryPerformanceFrequency()) * (SEC_TO_SEC % QueryPerformanceFrequency()) / QueryPerformanceFrequency() |
But still, we can reduce the risk of integer overflow even further since in most cases, the numerator can be made way smaller in most cases. With Since the frequency is only read once, IMO it's worth it to quickly compute a GCD. |
* Add process_time_times() helper function, called by _PyTime_GetProcessTimeWithInfo(). * Rename _PyTime_GetClockWithInfo() to py_clock(). * Remove _PyRuntimeState.time.
Avoid sysconf("_SC_CLK_TCK") call at Python startup. Only call it once at the first os.times() or time.process_time() call. * Add process_time_times() helper function, called by _PyTime_GetProcessTimeWithInfo(). * Rename _PyTime_GetClockWithInfo() to py_clock(). * Remove _PyRuntimeState.time.
And what if it is not reduced on some weird configuration? |
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Add process_time_times() helper function, called by _PyTime_GetProcessTimeWithInfo(). * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time().
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Add process_time_times() helper function, called by _PyTime_GetProcessTimeWithInfo(). * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES.
I only propose to make the current situation on known configuration better. If some platforms require very large numerator and denominator, later, we can consider to switch to int128_t for intermediate results in By the way, _PyTime_Mul() clamps the result to |
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Add process_time_times() helper function, called by _PyTime_GetProcessTimeWithInfo(). * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES.
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * Add process_time_times() helper function, called by py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution()
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Denominator (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Divisor (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
I merged my changes. @serhiy-storchaka: If you see room for improvement to reduce even more the risk of integer overflow in _PyTimeFraction_Mul(), go ahead ;-) |
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * Add process_time_times() helper function, called by py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Divisor (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
* Move _PyRuntimeState.time to _posixstate.ticks_per_second and time_module_state.ticks_per_second. * Add time_module_state.clocks_per_second. * Rename _PyTime_GetClockWithInfo() to py_clock(). * Rename _PyTime_GetProcessTimeWithInfo() to py_process_time(). * Add process_time_times() helper function, called by py_process_time(). * os.times() is now always built: no longer rely on HAVE_TIMES.
Use a fraction internally in the _PyTime API to reduce the risk of integer overflow: simplify the fraction using Greatest Common Divisor (GCD). The fraction API is used by time functions: perf_counter(), monotonic() and process_time(). For example, QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction SEC_TO_NS / frequency = 1_000_000_000 / 10_000_000 can be simplified to 100 / 1. * Add _PyTimeFraction type. * Add functions: * _PyTimeFraction_Set() * _PyTimeFraction_Mul() * _PyTimeFraction_Resolution() * No longer check "numer * denom <= _PyTime_MAX" in _PyTimeFraction_Set(). _PyTimeFraction_Mul() uses _PyTime_Mul() which handles integer overflow.
The
time.perf_counter()
function is implemented by callingQueryPerformanceCounter()
andQueryPerformanceFrequency()
. It computesQueryPerformanceCounter() * SEC_TO_SEC / QueryPerformanceFrequency()
using int64_t integers. The problem is thatSEC_TO_SEC
is big:10^9
.QueryPerformanceFrequency() usually returns 10 MHz on Windows 10 and newer. The fraction
SEC_TO_NS / frequency
=1_000_000_000 / 10_000_000
can be simplified to100 / 1
.I propose using a fraction internally to convert
QueryPerformanceCounter()
value to a number of seconds, and simplify the fraction using the Greatest Common Denominator (GCD).There are multiple functions using a fraction:
_PyTime_GetClockWithInfo()
forclock()
--time.process_time()
in Python_PyTime_GetProcessTimeWithInfo()
fortimes()
--time.process_time()
in Pythonpy_get_monotonic_clock()
formach_absolute_time()
on macOS --time.monotonic()
in Pythonpy_get_win_perf_counter()
forQueryPerformanceCounter()
on Windows --time.perf_counter()
in PythonLinked PRs
The text was updated successfully, but these errors were encountered: