https://github.com/python/cpython/commit/119bcfad9cac9ac8fa5fa6c0f3ee3ccfca778fe1 commit: 119bcfad9cac9ac8fa5fa6c0f3ee3ccfca778fe1 branch: main author: Chris Eibl <138194463+chris-e...@users.noreply.github.com> committer: picnixz <10796600+picn...@users.noreply.github.com> date: 2025-03-13T12:06:56+01:00 summary:
gh-129149: Add fast path for medium-sized integers in `PyLong_FromSsize_t()` (#129301) The implementation of `PyLong_FromLong()`, `PyLong_FromLongLong()` and `PyLong_FromSsize_t()` are now handled by a common macro `PYLONG_FROM_INT` which contains fast paths depending on the size of the integer to convert. Consequently, `PyLong_FromSsize_t()` for medium-sized integers is faster by roughly 25%. --------- Co-authored-by: Sergey B Kirpichev <skirpic...@gmail.com> files: A Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst M Lib/test/test_capi/test_long.py M Objects/longobject.c diff --git a/Lib/test/test_capi/test_long.py b/Lib/test/test_capi/test_long.py index a371f634432b6d..d3156645eeec2d 100644 --- a/Lib/test/test_capi/test_long.py +++ b/Lib/test/test_capi/test_long.py @@ -168,9 +168,9 @@ def check_long_asint(self, func, min_val, max_val, *, mask=False, negative_value_error=OverflowError): # round trip (object -> C integer -> object) - values = (0, 1, 1234, max_val) + values = (0, 1, 512, 1234, max_val) if min_val < 0: - values += (-1, min_val) + values += (-1, -512, -1234, min_val) for value in values: with self.subTest(value=value): self.assertEqual(func(value), value) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst new file mode 100644 index 00000000000000..b74607986d9a2e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-25-20-07-03.gh-issue-129149.njeFJi.rst @@ -0,0 +1,2 @@ +Add fast path for medium-size integers in :c:func:`PyLong_FromSsize_t`. +Patch by Chris Eibl. diff --git a/Objects/longobject.c b/Objects/longobject.c index ab5cfab74d8a34..51ecbd9e0ee0bd 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -332,49 +332,51 @@ _PyLong_Negate(PyLongObject **x_p) Py_DECREF(x); } +#define PYLONG_FROM_INT(UINT_TYPE, INT_TYPE, ival) \ + do { \ + /* Handle small and medium cases. */ \ + if (IS_SMALL_INT(ival)) { \ + return get_small_int((sdigit)(ival)); \ + } \ + if (-(INT_TYPE)PyLong_MASK <= (ival) && (ival) <= (INT_TYPE)PyLong_MASK) { \ + return _PyLong_FromMedium((sdigit)(ival)); \ + } \ + UINT_TYPE abs_ival = (ival) < 0 ? 0U-(UINT_TYPE)(ival) : (UINT_TYPE)(ival); \ + /* Do shift in two steps to avoid possible undefined behavior. */ \ + UINT_TYPE t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; \ + /* Count digits (at least two - smaller cases were handled above). */ \ + Py_ssize_t ndigits = 2; \ + while (t) { \ + ++ndigits; \ + t >>= PyLong_SHIFT; \ + } \ + /* Construct output value. */ \ + PyLongObject *v = long_alloc(ndigits); \ + if (v == NULL) { \ + return NULL; \ + } \ + digit *p = v->long_value.ob_digit; \ + _PyLong_SetSignAndDigitCount(v, (ival) < 0 ? -1 : 1, ndigits); \ + t = abs_ival; \ + while (t) { \ + *p++ = (digit)(t & PyLong_MASK); \ + t >>= PyLong_SHIFT; \ + } \ + return (PyObject *)v; \ + } while(0) + + /* Create a new int object from a C long int */ PyObject * PyLong_FromLong(long ival) { - PyLongObject *v; - unsigned long abs_ival, t; - int ndigits; - - /* Handle small and medium cases. */ - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - if (-(long)PyLong_MASK <= ival && ival <= (long)PyLong_MASK) { - return _PyLong_FromMedium((sdigit)ival); - } - - /* Count digits (at least two - smaller cases were handled above). */ - abs_ival = ival < 0 ? 0U-(unsigned long)ival : (unsigned long)ival; - /* Do shift in two steps to avoid possible undefined behavior. */ - t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; - ndigits = 2; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - - /* Construct output value. */ - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(unsigned long, long, ival); } #define PYLONG_FROM_UINT(INT_TYPE, ival) \ do { \ + /* Handle small and medium cases. */ \ if (IS_SMALL_UINT(ival)) { \ return get_small_int((sdigit)(ival)); \ } \ @@ -383,12 +385,13 @@ PyLong_FromLong(long ival) } \ /* Do shift in two steps to avoid possible undefined behavior. */ \ INT_TYPE t = (ival) >> PyLong_SHIFT >> PyLong_SHIFT; \ - /* Count the number of Python digits. */ \ + /* Count digits (at least two - smaller cases were handled above). */ \ Py_ssize_t ndigits = 2; \ while (t) { \ ++ndigits; \ t >>= PyLong_SHIFT; \ } \ + /* Construct output value. */ \ PyLongObject *v = long_alloc(ndigits); \ if (v == NULL) { \ return NULL; \ @@ -1482,40 +1485,7 @@ PyLong_AsVoidPtr(PyObject *vv) PyObject * PyLong_FromLongLong(long long ival) { - PyLongObject *v; - unsigned long long abs_ival, t; - int ndigits; - - /* Handle small and medium cases. */ - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - if (-(long long)PyLong_MASK <= ival && ival <= (long long)PyLong_MASK) { - return _PyLong_FromMedium((sdigit)ival); - } - - /* Count digits (at least two - smaller cases were handled above). */ - abs_ival = ival < 0 ? 0U-(unsigned long long)ival : (unsigned long long)ival; - /* Do shift in two steps to avoid possible undefined behavior. */ - t = abs_ival >> PyLong_SHIFT >> PyLong_SHIFT; - ndigits = 2; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - - /* Construct output value. */ - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, ival < 0 ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(unsigned long long, long long, ival); } /* Create a new int object from a C Py_ssize_t. */ @@ -1523,42 +1493,7 @@ PyLong_FromLongLong(long long ival) PyObject * PyLong_FromSsize_t(Py_ssize_t ival) { - PyLongObject *v; - size_t abs_ival; - size_t t; /* unsigned so >> doesn't propagate sign bit */ - int ndigits = 0; - int negative = 0; - - if (IS_SMALL_INT(ival)) { - return get_small_int((sdigit)ival); - } - - if (ival < 0) { - /* avoid signed overflow when ival = SIZE_T_MIN */ - abs_ival = (size_t)(-1-ival)+1; - negative = 1; - } - else { - abs_ival = (size_t)ival; - } - - /* Count the number of Python digits. */ - t = abs_ival; - while (t) { - ++ndigits; - t >>= PyLong_SHIFT; - } - v = long_alloc(ndigits); - if (v != NULL) { - digit *p = v->long_value.ob_digit; - _PyLong_SetSignAndDigitCount(v, negative ? -1 : 1, ndigits); - t = abs_ival; - while (t) { - *p++ = (digit)(t & PyLong_MASK); - t >>= PyLong_SHIFT; - } - } - return (PyObject *)v; + PYLONG_FROM_INT(size_t, Py_ssize_t, ival); } /* Get a C long long int from an int object or any object that has an _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com