diff --git a/Include/internal/pycore_long.h b/Include/internal/pycore_long.h index 8c1d017bb95e4e2..cae18944b195146 100644 --- a/Include/internal/pycore_long.h +++ b/Include/internal/pycore_long.h @@ -129,6 +129,9 @@ _PyLong_IsPositiveSingleDigit(PyObject* sub) { return ((size_t)signed_size) <= 1; } +long _PyLong_AsLongAndOverflow(const PyLongObject *v, int *overflow); + + #ifdef __cplusplus } #endif diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-01-03-20-59-20.gh-issue-100726.W9huFl.rst b/Misc/NEWS.d/next/Core and Builtins/2023-01-03-20-59-20.gh-issue-100726.W9huFl.rst new file mode 100644 index 000000000000000..2c93098b347a7f5 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-01-03-20-59-20.gh-issue-100726.W9huFl.rst @@ -0,0 +1 @@ +Optimize construction of ``range`` object for medium size integers. diff --git a/Objects/longobject.c b/Objects/longobject.c index 1db4ca418e066ec..5dd515a370a3d0d 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -474,45 +474,21 @@ PyLong_FromDouble(double dval) #define PY_ABS_LONG_MIN (0-(unsigned long)LONG_MIN) #define PY_ABS_SSIZE_T_MIN (0-(size_t)PY_SSIZE_T_MIN) -/* Get a C long int from an int object or any object that has an __index__ - method. +/* Get a C long int from a PyLong object On overflow, return -1 and set *overflow to 1 or -1 depending on the sign of - the result. Otherwise *overflow is 0. + the result. Otherwise *overflow is unchanged. - For other errors (e.g., TypeError), return -1 and set an error condition. - In this case *overflow will be 0. + Internal version, does not perform error checking */ - -long -PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) +inline long +_PyLong_AsLongAndOverflow(const PyLongObject *v, int *overflow) { - /* This version by Tim Peters */ - PyLongObject *v; - unsigned long x, prev; - long res; - Py_ssize_t i; - int sign; - int do_decref = 0; /* if PyNumber_Index was called */ - - *overflow = 0; - if (vv == NULL) { - PyErr_BadInternalCall(); - return -1; - } - - if (PyLong_Check(vv)) { - v = (PyLongObject *)vv; - } - else { - v = (PyLongObject *)_PyNumber_Index(vv); - if (v == NULL) - return -1; - do_decref = 1; - } + assert(v != NULL); + assert( PyLong_Check(v) ); - res = -1; - i = Py_SIZE(v); + long res = -1; + Py_ssize_t i = Py_SIZE(v); switch (i) { case -1: @@ -525,14 +501,15 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) res = v->ob_digit[0]; break; default: - sign = 1; - x = 0; + { + int sign = 1; + unsigned long x = 0; if (i < 0) { sign = -1; i = -(i); } while (--i >= 0) { - prev = x; + unsigned long prev = x; x = (x << PyLong_SHIFT) | v->ob_digit[i]; if ((x >> PyLong_SHIFT) != prev) { *overflow = sign; @@ -553,7 +530,46 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) /* res is already set to -1 */ } } + } exit: + return res; +} + + +/* Get a C long int from an int object or any object that has an __index__ + method. + + On overflow, return -1 and set *overflow to 1 or -1 depending on the sign of + the result. Otherwise *overflow is 0. + + For other errors (e.g., TypeError), return -1 and set an error condition. + In this case *overflow will be 0. +*/ +long +PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) +{ + /* This version by Tim Peters */ + PyLongObject *v; + int do_decref = 0; /* if PyNumber_Index was called */ + *overflow = 0; + + if (vv == NULL) { + PyErr_BadInternalCall(); + return -1; + } + + if (PyLong_Check(vv)) { + v = (PyLongObject *)vv; + } + else { + v = (PyLongObject *)_PyNumber_Index(vv); + if (v == NULL) + return -1; + do_decref = 1; + } + + long res = _PyLong_AsLongAndOverflow(v, overflow); + if (do_decref) { Py_DECREF(v); } diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index 992e7c079ded54f..7740f45f7107692 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -171,6 +171,28 @@ range_dealloc(rangeobject *r) PyObject_Free(r); } +static unsigned long +get_len_of_range(long lo, long hi, long step); + +/// Return the length as a long, or -1 on error +static inline long compute_range_length_long(PyLongObject *start, PyLongObject *stop, PyLongObject *step) { + int overflow = 0; + + long long_start = _PyLong_AsLongAndOverflow(start, &overflow); + if (overflow) { + return -1; + } + long long_stop = _PyLong_AsLongAndOverflow(stop, &overflow); + if (overflow) { + return -1; + } + long long_step = _PyLong_AsLongAndOverflow(step, &overflow); + if (overflow) { + return -1; + } + return get_len_of_range(long_start, long_stop, long_step); +} + /* Return number of items in range (lo, hi, step) as a PyLong object, * when arguments are PyLong objects. Arguments MUST return 1 with * PyLong_Check(). Return NULL when there is an error. @@ -182,6 +204,12 @@ compute_range_length(PyObject *start, PyObject *stop, PyObject *step) Algorithm is equal to that of get_len_of_range(), but it operates on PyObjects (which are assumed to be PyLong objects). ---------------------------------------------------------------*/ + + // fast path when all arguments fit into a long integer + long len = compute_range_length_long((PyLongObject*)start, (PyLongObject*)stop, (PyLongObject*)step); + if (len>=0) { + return PyLong_FromLong(len); + } int cmp_result; PyObject *lo, *hi; PyObject *diff = NULL;