Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bdaeb7d

Browse files
authored
bpo-31773: _PyTime_GetPerfCounter() uses _PyTime_t (GH-3983)
* Rewrite win_perf_counter() to only use integers internally. * Add _PyTime_MulDiv() which compute "ticks * mul / div" in two parts (int part and remaining) to prevent integer overflow. * Clock frequency is checked at initialization for integer overflow. * Enhance also pymonotonic() to reduce the precision loss on macOS (mach_absolute_time() clock).
1 parent 0df1905 commit bdaeb7d

4 files changed

Lines changed: 135 additions & 48 deletions

File tree

Include/pytime.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,16 +197,16 @@ PyAPI_FUNC(int) _PyTime_gmtime(time_t t, struct tm *tm);
197197
198198
The function cannot fail. _PyTime_Init() ensures that the system clock
199199
works. */
200-
PyAPI_FUNC(double) _PyTime_GetPerfCounterDouble(void);
200+
PyAPI_FUNC(_PyTime_t) _PyTime_GetPerfCounter(void);
201201

202202
/* Get the performance counter: clock with the highest available resolution to
203203
measure a short duration.
204204
205205
Fill info (if set) with information of the function used to get the time.
206206
207207
Return 0 on success, raise an exception and return -1 on error. */
208-
PyAPI_FUNC(int) _PyTime_GetPerfCounterDoubleWithInfo(
209-
double *t,
208+
PyAPI_FUNC(int) _PyTime_GetPerfCounterWithInfo(
209+
_PyTime_t *t,
210210
_Py_clock_info_t *info);
211211

212212
#ifdef __cplusplus

Modules/timemodule.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,12 @@ floatclock(_Py_clock_info_t *info)
9191
static PyObject*
9292
perf_counter(_Py_clock_info_t *info)
9393
{
94-
double t;
95-
if (_PyTime_GetPerfCounterDoubleWithInfo(&t, info) < 0) {
94+
_PyTime_t t;
95+
if (_PyTime_GetPerfCounterWithInfo(&t, info) < 0) {
9696
return NULL;
9797
}
98-
return PyFloat_FromDouble(t);
98+
double d = _PyTime_AsSecondsDouble(t);
99+
return PyFloat_FromDouble(d);
99100
}
100101

101102
#if defined(MS_WINDOWS) || defined(HAVE_CLOCK)

Python/import.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,10 +1669,10 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
16691669
else {
16701670
static int ximporttime = 0;
16711671
static int import_level;
1672-
static double accumulated;
1672+
static _PyTime_t accumulated;
16731673
_Py_IDENTIFIER(importtime);
16741674

1675-
double t1 = 0, accumulated_copy = accumulated;
1675+
_PyTime_t t1 = 0, accumulated_copy = accumulated;
16761676

16771677
Py_XDECREF(mod);
16781678

@@ -1695,7 +1695,7 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
16951695

16961696
if (ximporttime) {
16971697
import_level++;
1698-
t1 = _PyTime_GetPerfCounterDouble();
1698+
t1 = _PyTime_GetPerfCounter();
16991699
accumulated = 0;
17001700
}
17011701

@@ -1711,12 +1711,12 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *globals,
17111711
mod != NULL);
17121712

17131713
if (ximporttime) {
1714-
double cum = _PyTime_GetPerfCounterDouble() - t1;
1714+
_PyTime_t cum = _PyTime_GetPerfCounter() - t1;
17151715

17161716
import_level--;
17171717
fprintf(stderr, "import time: %9ld | %10ld | %*s%s\n",
1718-
(long)ceil((cum - accumulated) * 1e6),
1719-
(long)ceil(cum * 1e6),
1718+
(long)_PyTime_AsMicroseconds(cum - accumulated, _PyTime_ROUND_CEILING),
1719+
(long)_PyTime_AsMicroseconds(cum, _PyTime_ROUND_CEILING),
17201720
import_level*2, "", PyUnicode_AsUTF8(abs_name));
17211721

17221722
accumulated = accumulated_copy + cum;

Python/pytime.c

Lines changed: 122 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,27 @@ _PyTime_overflow(void)
4242
"timestamp too large to convert to C _PyTime_t");
4343
}
4444

45+
46+
#if defined(MS_WINDOWS) || defined(__APPLE__)
47+
Py_LOCAL_INLINE(_PyTime_t)
48+
_PyTime_MulDiv(_PyTime_t ticks, _PyTime_t mul, _PyTime_t div)
49+
{
50+
_PyTime_t intpart, remaining;
51+
/* Compute (ticks * mul / div) in two parts to prevent integer overflow:
52+
compute integer part, and then the remaining part.
53+
54+
(ticks * mul) / div == (ticks / div) * mul + (ticks % div) * mul / div
55+
56+
The caller must ensure that "(div - 1) * mul" cannot overflow. */
57+
intpart = ticks / div;
58+
ticks %= div;
59+
remaining = ticks * mul;
60+
remaining /= div;
61+
return intpart * mul + remaining;
62+
}
63+
#endif /* defined(MS_WINDOWS) || defined(__APPLE__) */
64+
65+
4566
time_t
4667
_PyLong_AsTime_t(PyObject *obj)
4768
{
@@ -700,29 +721,62 @@ pymonotonic(_PyTime_t *tp, _Py_clock_info_t *info, int raise)
700721

701722
#elif defined(__APPLE__)
702723
static mach_timebase_info_data_t timebase;
703-
uint64_t time;
724+
static uint64_t t0 = 0;
725+
uint64_t ticks;
704726

705727
if (timebase.denom == 0) {
706728
/* According to the Technical Q&A QA1398, mach_timebase_info() cannot
707729
fail: https://developer.apple.com/library/mac/#qa/qa1398/ */
708730
(void)mach_timebase_info(&timebase);
709-
}
710731

711-
time = mach_absolute_time();
732+
/* Sanity check: should never occur in practice */
733+
if (timebase.numer < 1 || timebase.denom < 1) {
734+
PyErr_SetString(PyExc_RuntimeError,
735+
"invalid mach_timebase_info");
736+
return -1;
737+
}
738+
739+
/* Check that timebase.numer and timebase.denom can be casted to
740+
_PyTime_t. In pratice, timebase uses uint32_t, so casting cannot
741+
overflow. At the end, only make sure that the type is uint32_t
742+
(_PyTime_t is 64-bit long). */
743+
assert(sizeof(timebase.numer) < sizeof(_PyTime_t));
744+
assert(sizeof(timebase.denom) < sizeof(_PyTime_t));
712745

713-
/* apply timebase factor */
714-
time *= timebase.numer;
715-
time /= timebase.denom;
746+
/* Make sure that (ticks * timebase.numer) cannot overflow in
747+
_PyTime_MulDiv(), with ticks < timebase.denom.
716748
717-
*tp = time;
749+
Known time bases:
750+
751+
* always (1, 1) on Intel
752+
* (1000000000, 33333335) or (1000000000, 25000000) on PowerPC
753+
754+
None of these time bases can overflow with 64-bit _PyTime_t, but
755+
check for overflow, just in case. */
756+
if ((_PyTime_t)timebase.numer > _PyTime_MAX / (_PyTime_t)timebase.denom) {
757+
PyErr_SetString(PyExc_OverflowError,
758+
"mach_timebase_info is too large");
759+
return -1;
760+
}
761+
762+
t0 = mach_absolute_time();
763+
}
718764

719765
if (info) {
720766
info->implementation = "mach_absolute_time()";
721-
info->resolution = (double)timebase.numer / timebase.denom * 1e-9;
767+
info->resolution = (double)timebase.numer / (double)timebase.denom * 1e-9;
722768
info->monotonic = 1;
723769
info->adjustable = 0;
724770
}
725771

772+
ticks = mach_absolute_time();
773+
/* Use a "time zero" to reduce precision loss when converting time
774+
to floatting point number, as in time.monotonic(). */
775+
ticks -= t0;
776+
*tp = _PyTime_MulDiv(ticks,
777+
(_PyTime_t)timebase.numer,
778+
(_PyTime_t)timebase.denom);
779+
726780
#elif defined(__hpux)
727781
hrtime_t time;
728782

@@ -802,60 +856,93 @@ _PyTime_GetMonotonicClockWithInfo(_PyTime_t *tp, _Py_clock_info_t *info)
802856

803857
#ifdef MS_WINDOWS
804858
static int
805-
win_perf_counter(double *tp, _Py_clock_info_t *info)
859+
win_perf_counter(_PyTime_t *tp, _Py_clock_info_t *info)
806860
{
807-
static LONGLONG cpu_frequency = 0;
808-
static LONGLONG ctrStart;
861+
static LONGLONG frequency = 0;
862+
static LONGLONG t0 = 0;
809863
LARGE_INTEGER now;
810-
double diff;
864+
LONGLONG ticksll;
865+
_PyTime_t ticks;
811866

812-
if (cpu_frequency == 0) {
867+
if (frequency == 0) {
813868
LARGE_INTEGER freq;
814-
QueryPerformanceCounter(&now);
815-
ctrStart = now.QuadPart;
816-
if (!QueryPerformanceFrequency(&freq) || freq.QuadPart == 0) {
869+
if (!QueryPerformanceFrequency(&freq)) {
817870
PyErr_SetFromWindowsErr(0);
818871
return -1;
819872
}
820-
cpu_frequency = freq.QuadPart;
873+
frequency = freq.QuadPart;
874+
875+
/* Sanity check: should never occur in practice */
876+
if (frequency < 1) {
877+
PyErr_SetString(PyExc_RuntimeError,
878+
"invalid QueryPerformanceFrequency");
879+
return -1;
880+
}
881+
882+
/* Check that frequency can be casted to _PyTime_t.
883+
884+
Make also sure that (ticks * SEC_TO_NS) cannot overflow in
885+
_PyTime_MulDiv(), with ticks < frequency.
886+
887+
Known QueryPerformanceFrequency() values:
888+
889+
* 10,000,000 (10 MHz): 100 ns resolution
890+
* 3,579,545 Hz (3.6 MHz): 279 ns resolution
891+
892+
None of these frequencies can overflow with 64-bit _PyTime_t, but
893+
check for overflow, just in case. */
894+
if (frequency > _PyTime_MAX
895+
|| frequency > (LONGLONG)_PyTime_MAX / (LONGLONG)SEC_TO_NS) {
896+
PyErr_SetString(PyExc_OverflowError,
897+
"QueryPerformanceFrequency is too large");
898+
return -1;
899+
}
900+
901+
QueryPerformanceCounter(&now);
902+
t0 = now.QuadPart;
821903
}
822-
QueryPerformanceCounter(&now);
823-
diff = (double)(now.QuadPart - ctrStart);
904+
824905
if (info) {
825906
info->implementation = "QueryPerformanceCounter()";
826-
info->resolution = 1.0 / (double)cpu_frequency;
907+
info->resolution = 1.0 / (double)frequency;
827908
info->monotonic = 1;
828909
info->adjustable = 0;
829910
}
830911

831-
diff = diff / (double)cpu_frequency;
832-
*tp = diff;
912+
QueryPerformanceCounter(&now);
913+
ticksll = now.QuadPart;
914+
915+
/* Use a "time zero" to reduce precision loss when converting time
916+
to floatting point number, as in time.perf_counter(). */
917+
ticksll -= t0;
918+
919+
/* Make sure that casting LONGLONG to _PyTime_t cannot overflow,
920+
both types are signed */
921+
Py_BUILD_ASSERT(sizeof(ticksll) <= sizeof(ticks));
922+
ticks = (_PyTime_t)ticksll;
923+
924+
*tp = _PyTime_MulDiv(ticks, SEC_TO_NS, (_PyTime_t)frequency);
833925
return 0;
834926
}
835927
#endif
836928

837929

838930
int
839-
_PyTime_GetPerfCounterDoubleWithInfo(double *d, _Py_clock_info_t *info)
931+
_PyTime_GetPerfCounterWithInfo(_PyTime_t *t, _Py_clock_info_t *info)
840932
{
841933
#ifdef MS_WINDOWS
842-
return win_perf_counter(d, info);
934+
return win_perf_counter(t, info);
843935
#else
844-
_PyTime_t t;
845-
if (_PyTime_GetMonotonicClockWithInfo(&t, info) < 0) {
846-
return -1;
847-
}
848-
*d = _PyTime_AsSecondsDouble(t);
849-
return 0;
936+
return _PyTime_GetMonotonicClockWithInfo(t, info);
850937
#endif
851938
}
852939

853940

854-
double
855-
_PyTime_GetPerfCounterDouble(void)
941+
_PyTime_t
942+
_PyTime_GetPerfCounter(void)
856943
{
857-
double t;
858-
if (_PyTime_GetPerfCounterDoubleWithInfo(&t, NULL)) {
944+
_PyTime_t t;
945+
if (_PyTime_GetPerfCounterWithInfo(&t, NULL)) {
859946
Py_UNREACHABLE();
860947
}
861948
return t;
@@ -869,14 +956,13 @@ _PyTime_Init(void)
869956
are working properly to not have to check for exceptions at runtime. If
870957
a clock works once, it cannot fail in next calls. */
871958
_PyTime_t t;
872-
double d;
873959
if (_PyTime_GetSystemClockWithInfo(&t, NULL) < 0) {
874960
return -1;
875961
}
876962
if (_PyTime_GetMonotonicClockWithInfo(&t, NULL) < 0) {
877963
return -1;
878964
}
879-
if (_PyTime_GetPerfCounterDoubleWithInfo(&d, NULL) < 0) {
965+
if (_PyTime_GetPerfCounterWithInfo(&t, NULL) < 0) {
880966
return -1;
881967
}
882968
return 0;

0 commit comments

Comments
 (0)