From 5dab8e2fcabfa6cd37d313376878ca2b87519eed Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Thu, 29 Aug 2019 18:39:50 +0500 Subject: [PATCH 1/3] bpo-37986: Improve perfomance of PyLong_FromDouble() --- .../2019-11-20-09-50-58.bpo-37986.o0lmA7.rst | 4 ++++ Objects/floatobject.c | 22 +------------------ Objects/longobject.c | 18 +++++++++++++-- 3 files changed, 21 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst new file mode 100644 index 00000000000000..4bc97168bbef8c --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst @@ -0,0 +1,4 @@ +Improve performance of :c:func:`PyLong_FromDouble` for values that fit into +:c:type:`long`. Now :meth:`float.__trunc__` is faster up to 10%, +:func:`math.floor()` and :func:`math.ceil()` are faster up to 30% when used +with such values. diff --git a/Objects/floatobject.c b/Objects/floatobject.c index 20a5155e07346d..e259dd61981ee5 100644 --- a/Objects/floatobject.c +++ b/Objects/floatobject.c @@ -882,27 +882,7 @@ static PyObject * float___trunc___impl(PyObject *self) /*[clinic end generated code: output=dd3e289dd4c6b538 input=591b9ba0d650fdff]*/ { - double x = PyFloat_AsDouble(self); - double wholepart; /* integral portion of x, rounded toward 0 */ - - (void)modf(x, &wholepart); - /* Try to get out cheap if this fits in a Python int. The attempt - * to cast to long must be protected, as C doesn't define what - * happens if the double is too big to fit in a long. Some rare - * systems raise an exception then (RISCOS was mentioned as one, - * and someone using a non-default option on Sun also bumped into - * that). Note that checking for >= and <= LONG_{MIN,MAX} would - * still be vulnerable: if a long has more bits of precision than - * a double, casting MIN/MAX to double may yield an approximation, - * and if that's rounded up, then, e.g., wholepart=LONG_MAX+1 would - * yield true from the C expression wholepart<=LONG_MAX, despite - * that wholepart is actually greater than LONG_MAX. - */ - if (LONG_MIN < wholepart && wholepart < LONG_MAX) { - const long aslong = (long)wholepart; - return PyLong_FromLong(aslong); - } - return PyLong_FromDouble(wholepart); + return PyLong_FromDouble(PyFloat_AS_DOUBLE(self)); } /* double_round: rounds a finite double to the closest multiple of diff --git a/Objects/longobject.c b/Objects/longobject.c index 2b57ea18666eec..a1e6bd0f8f8814 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -433,6 +433,21 @@ PyLong_FromSize_t(size_t ival) PyObject * PyLong_FromDouble(double dval) { + /* Try to get out cheap if this fits in a long. When a finite value of real + * floating type is converted to an integer type, the value is truncated + * toward zero. If the value of the integral part cannot be represented by + * the integer type, the behavior is undefined. Thus, we must check that + * value is in range (LONG_MIN - 1, LONG_MAX + 1). If a long has more bits + * of precision than a double, casting LONG_MIN - 1 to double may yield an + * approximation, but LONG_MAX + 1 is a power of two and can be represented + * as double exactly (assuming FLT_RADIX is 2 or 16), so for simplicity + * check against [-(LONG_MAX + 1), LONG_MAX + 1). + */ + const double int_max = (unsigned long)LONG_MAX + 1; + if (-int_max <= dval && dval < int_max) { + return PyLong_FromLong((long)dval); + } + PyLongObject *v; double frac; int i, ndig, expo, neg; @@ -452,8 +467,7 @@ PyLong_FromDouble(double dval) dval = -dval; } frac = frexp(dval, &expo); /* dval = frac*2**expo; 0.0 <= frac < 1.0 */ - if (expo <= 0) - return PyLong_FromLong(0L); + assert(expo > 0); ndig = (expo-1) / PyLong_SHIFT + 1; /* Number of 'digits' in result */ v = _PyLong_New(ndig); if (v == NULL) From 0b90ead7d23be2fced6b547f4d6426a902e04a7f Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 10 May 2020 09:46:07 +0100 Subject: [PATCH 2/3] Use strict bound check for safety and symmetry --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index a1e6bd0f8f8814..ea08b8fbe97517 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -444,7 +444,7 @@ PyLong_FromDouble(double dval) * check against [-(LONG_MAX + 1), LONG_MAX + 1). */ const double int_max = (unsigned long)LONG_MAX + 1; - if (-int_max <= dval && dval < int_max) { + if (-int_max < dval && dval < int_max) { return PyLong_FromLong((long)dval); } From 8be513e45e68bd525aec8a3f49040102d0d74ed1 Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Sun, 10 May 2020 09:51:25 +0100 Subject: [PATCH 3/3] Remove possibly outdated performance claims --- .../2019-11-20-09-50-58.bpo-37986.o0lmA7.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst index 4bc97168bbef8c..62446e35ae01ba 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2019-11-20-09-50-58.bpo-37986.o0lmA7.rst @@ -1,4 +1,2 @@ Improve performance of :c:func:`PyLong_FromDouble` for values that fit into -:c:type:`long`. Now :meth:`float.__trunc__` is faster up to 10%, -:func:`math.floor()` and :func:`math.ceil()` are faster up to 30% when used -with such values. +:c:type:`long`.