From d3fb72a8dcf577edd9b4a97594bd14759dcd2d12 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 16 Jun 2025 22:55:00 +0200 Subject: [PATCH 01/13] x --- Objects/longobject.c | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 2b533312fee673..e80154cf0762b5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -511,7 +511,7 @@ PyLong_FromDouble(double dval) long PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) { - /* This version by Tim Peters */ + /* This version originally by Tim Peters */ PyLongObject *v; unsigned long x, prev; long res; @@ -565,6 +565,36 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) goto exit; } } + +/* + digit *digits = v->long_value.ob_digit; + + assert(i >= 2); + #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) + /* use 2 digits * + --i; + x = digits[i]; + x <<= PyLong_SHIFT; + --i; + x |= digits[i]; + #else + /* use 1 digit * + //--i; + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + //x = digits[i]; + x=0; + #endif +*/ + +/* + while (--i >= 0) { + if (x > SIZE_MAX >> PyLong_SHIFT) { + *overflow = sign; + goto exit; + } + x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; + } +*/ /* Haven't lost any bits, but casting to long requires extra * care (see comment above). */ From a38cbfba8f7975cae212b8bbf22c3528a923f85a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Mon, 16 Jun 2025 23:38:32 +0200 Subject: [PATCH 02/13] Improve performance of pylong_as_xxx conversions --- Objects/longobject.c | 87 +++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index ca22574437a270..0154358d384017 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -513,7 +513,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) { /* This version originally by Tim Peters */ PyLongObject *v; - unsigned long x, prev; + unsigned long x; long res; Py_ssize_t i; int sign; @@ -556,37 +556,22 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) res = -1; i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = 0; - while (--i >= 0) { - prev = x; - x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; - if ((x >> PyLong_SHIFT) != prev) { - *overflow = sign; - goto exit; - } - } -/* digit *digits = v->long_value.ob_digit; - assert(i >= 2); - #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) - /* use 2 digits * + /* unroll 1 digit */ --i; + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); x = digits[i]; + + #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) + /* unroll another digit */ x <<= PyLong_SHIFT; --i; x |= digits[i]; - #else - /* use 1 digit * - //--i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - //x = digits[i]; - x=0; #endif -*/ -/* + while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { *overflow = sign; @@ -594,7 +579,6 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) } x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } -*/ /* Haven't lost any bits, but casting to long requires extra * care (see comment above). */ @@ -657,7 +641,7 @@ PyLong_AsInt(PyObject *obj) Py_ssize_t PyLong_AsSsize_t(PyObject *vv) { PyLongObject *v; - size_t x, prev; + size_t x; Py_ssize_t i; int sign; @@ -676,12 +660,19 @@ PyLong_AsSsize_t(PyObject *vv) { } i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = 0; + + digit *digits = v->long_value.ob_digit; + assert(i >= 2); + /* unroll 1 digit */ + --i; + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + x = digits[i]; + while (--i >= 0) { - prev = x; - x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; - if ((x >> PyLong_SHIFT) != prev) + if (x > SIZE_MAX >> PyLong_SHIFT) { goto overflow; + } + x = (x << PyLong_SHIFT) | digits[i]; } /* Haven't lost any bits, but casting to a signed type requires * extra care (see comment above). @@ -707,7 +698,7 @@ unsigned long PyLong_AsUnsignedLong(PyObject *vv) { PyLongObject *v; - unsigned long x, prev; + unsigned long x; Py_ssize_t i; if (vv == NULL) { @@ -738,13 +729,19 @@ PyLong_AsUnsignedLong(PyObject *vv) return (unsigned long) -1; } i = _PyLong_DigitCount(v); - x = 0; + + digit *digits = v->long_value.ob_digit; + assert(i >= 2); + /* unroll 1 digit */ + --i; + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + x = digits[i]; + while (--i >= 0) { - prev = x; - x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; - if ((x >> PyLong_SHIFT) != prev) { + if (x > SIZE_MAX >> PyLong_SHIFT) { goto overflow; } + x = (x << PyLong_SHIFT) | digits[i]; } return x; overflow: @@ -761,7 +758,7 @@ size_t PyLong_AsSize_t(PyObject *vv) { PyLongObject *v; - size_t x, prev; + size_t x; Py_ssize_t i; if (vv == NULL) { @@ -783,16 +780,22 @@ PyLong_AsSize_t(PyObject *vv) return (size_t) -1; } i = _PyLong_DigitCount(v); - x = 0; + + digit *digits = v->long_value.ob_digit; + assert(i >= 2); + /* unroll 1 digit */ + --i; + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + x = digits[i]; + while (--i >= 0) { - prev = x; - x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; - if ((x >> PyLong_SHIFT) != prev) { - PyErr_SetString(PyExc_OverflowError, - "Python int too large to convert to C size_t"); - return (size_t) -1; + if (x > SIZE_MAX >> PyLong_SHIFT) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C size_t"); + return (size_t) -1; + } + x = (x << PyLong_SHIFT) | digits[i]; } - } return x; } From 588490abc32359f02a4e40b1a5f21869ced5f966 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 17 Jun 2025 10:35:07 +0200 Subject: [PATCH 03/13] refactor --- Objects/longobject.c | 71 ++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 0154358d384017..56a24a7adf83f5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -498,6 +498,26 @@ 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) +static inline unsigned long +_unroll_digits(PyLongObject *v, Py_ssize_t *i) +{ + digit *digits = v->long_value.ob_digit; + assert(*digit_count >= 2); + /* unroll 1 digit */ + --(*i); + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + unsigned long x = digits[*i]; + + #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) + /* unroll another digit */ + x <<= PyLong_SHIFT; + --(*i); + x |= digits[*i]; + #endif + + return x; +} + /* Get a C long int from an int object or any object that has an __index__ method. @@ -507,7 +527,6 @@ PyLong_FromDouble(double dval) 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) { @@ -557,21 +576,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - digit *digits = v->long_value.ob_digit; - assert(i >= 2); - /* unroll 1 digit */ - --i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - x = digits[i]; - - #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) - /* unroll another digit */ - x <<= PyLong_SHIFT; - --i; - x |= digits[i]; - #endif - - + x = _unroll_digits(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { *overflow = sign; @@ -661,18 +666,12 @@ PyLong_AsSsize_t(PyObject *vv) { i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - digit *digits = v->long_value.ob_digit; - assert(i >= 2); - /* unroll 1 digit */ - --i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - x = digits[i]; - + x = _unroll_digits(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { goto overflow; } - x = (x << PyLong_SHIFT) | digits[i]; + x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } /* Haven't lost any bits, but casting to a signed type requires * extra care (see comment above). @@ -730,18 +729,12 @@ PyLong_AsUnsignedLong(PyObject *vv) } i = _PyLong_DigitCount(v); - digit *digits = v->long_value.ob_digit; - assert(i >= 2); - /* unroll 1 digit */ - --i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - x = digits[i]; - + x = _unroll_digits(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { goto overflow; } - x = (x << PyLong_SHIFT) | digits[i]; + x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } return x; overflow: @@ -781,20 +774,14 @@ PyLong_AsSize_t(PyObject *vv) } i = _PyLong_DigitCount(v); - digit *digits = v->long_value.ob_digit; - assert(i >= 2); - /* unroll 1 digit */ - --i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - x = digits[i]; - + x = _unroll_digits(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C size_t"); return (size_t) -1; } - x = (x << PyLong_SHIFT) | digits[i]; + x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } return x; } @@ -823,7 +810,7 @@ _PyLong_AsUnsignedLongMask(PyObject *vv) } i = _PyLong_DigitCount(v); int sign = _PyLong_NonCompactSign(v); - x = 0; + x = _unroll_digits(v, &i); while (--i >= 0) { x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } @@ -1642,7 +1629,7 @@ _PyLong_AsUnsignedLongLongMask(PyObject *vv) } i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = 0; + x = _unroll_digits(v, &i); while (--i >= 0) { x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } From 8031af02bde65e59d911827be79bfbe238fce292 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 17 Jun 2025 10:37:10 +0200 Subject: [PATCH 04/13] fix assert --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 56a24a7adf83f5..e94b59854fc8d5 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -502,7 +502,7 @@ static inline unsigned long _unroll_digits(PyLongObject *v, Py_ssize_t *i) { digit *digits = v->long_value.ob_digit; - assert(*digit_count >= 2); + assert(*i >= 2); /* unroll 1 digit */ --(*i); assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); From 01c48ae2c8e667f47b795fa3c042a3e4090f40c7 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:37:51 +0000 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst new file mode 100644 index 00000000000000..d7f42b870e605d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst @@ -0,0 +1 @@ +Improve performance of ``PyLongObject`` conversion method ``PyLong_AsLongAndOverflow``. From 401d860c9b98284d63326c62bf773408608def46 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 17 Jun 2025 16:46:25 +0200 Subject: [PATCH 06/13] Update Objects/longobject.c Co-authored-by: Victor Stinner --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index e94b59854fc8d5..6391459c1c2aef 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -578,7 +578,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) x = _unroll_digits(v, &i); while (--i >= 0) { - if (x > SIZE_MAX >> PyLong_SHIFT) { + if (x > (ULONG_MAX >> PyLong_SHIFT)) { *overflow = sign; goto exit; } From 750c676dcb5c0e8917c993c8b5b1866ae117f720 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 17 Jun 2025 19:50:25 +0200 Subject: [PATCH 07/13] test --- Objects/longobject.c | 50 +++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 6391459c1c2aef..90af1d3bbfcf57 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -499,7 +499,7 @@ PyLong_FromDouble(double dval) #define PY_ABS_SSIZE_T_MIN (0-(size_t)PY_SSIZE_T_MIN) static inline unsigned long -_unroll_digits(PyLongObject *v, Py_ssize_t *i) +unroll_digits_ulong(PyLongObject *v, Py_ssize_t *i) { digit *digits = v->long_value.ob_digit; assert(*i >= 2); @@ -518,6 +518,26 @@ _unroll_digits(PyLongObject *v, Py_ssize_t *i) return x; } +static inline size_t +unroll_digits_size_t(PyLongObject *v, Py_ssize_t *i) +{ + digit *digits = v->long_value.ob_digit; + assert(*i >= 2); + /* unroll 1 digit */ + --(*i); + assert(SIZE_MAX >= ((1UL << PyLong_SHIFT) - 1)); + size_t x = digits[*i]; + + #if ( (SIZE_MAX >> PyLong_SHIFT) >= ( ( 1 << PyLong_SHIFT) - 1) ) + /* unroll another digit */ + x <<= PyLong_SHIFT; + --(*i); + x |= digits[*i]; + #endif + + return x; +} + /* Get a C long int from an int object or any object that has an __index__ method. @@ -532,7 +552,6 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) { /* This version originally by Tim Peters */ PyLongObject *v; - unsigned long x; long res; Py_ssize_t i; int sign; @@ -576,7 +595,7 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow) i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = _unroll_digits(v, &i); + unsigned long x = unroll_digits_ulong(v, &i); while (--i >= 0) { if (x > (ULONG_MAX >> PyLong_SHIFT)) { *overflow = sign; @@ -646,7 +665,6 @@ PyLong_AsInt(PyObject *obj) Py_ssize_t PyLong_AsSsize_t(PyObject *vv) { PyLongObject *v; - size_t x; Py_ssize_t i; int sign; @@ -666,7 +684,7 @@ PyLong_AsSsize_t(PyObject *vv) { i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = _unroll_digits(v, &i); + size_t x = unroll_digits_size_t(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { goto overflow; @@ -697,7 +715,6 @@ unsigned long PyLong_AsUnsignedLong(PyObject *vv) { PyLongObject *v; - unsigned long x; Py_ssize_t i; if (vv == NULL) { @@ -729,9 +746,9 @@ PyLong_AsUnsignedLong(PyObject *vv) } i = _PyLong_DigitCount(v); - x = _unroll_digits(v, &i); + unsigned long x = unroll_digits_ulong(v, &i); while (--i >= 0) { - if (x > SIZE_MAX >> PyLong_SHIFT) { + if (x > ULONG_MAX >> PyLong_SHIFT) { goto overflow; } x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; @@ -751,7 +768,6 @@ size_t PyLong_AsSize_t(PyObject *vv) { PyLongObject *v; - size_t x; Py_ssize_t i; if (vv == NULL) { @@ -774,7 +790,7 @@ PyLong_AsSize_t(PyObject *vv) } i = _PyLong_DigitCount(v); - x = _unroll_digits(v, &i); + size_t x = unroll_digits_size_t(v, &i); while (--i >= 0) { if (x > SIZE_MAX >> PyLong_SHIFT) { PyErr_SetString(PyExc_OverflowError, @@ -793,7 +809,6 @@ static unsigned long _PyLong_AsUnsignedLongMask(PyObject *vv) { PyLongObject *v; - unsigned long x; Py_ssize_t i; if (vv == NULL || !PyLong_Check(vv)) { @@ -810,7 +825,7 @@ _PyLong_AsUnsignedLongMask(PyObject *vv) } i = _PyLong_DigitCount(v); int sign = _PyLong_NonCompactSign(v); - x = _unroll_digits(v, &i); + unsigned long x = unroll_digits_ulong(v, &i); while (--i >= 0) { x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } @@ -1611,7 +1626,6 @@ static unsigned long long _PyLong_AsUnsignedLongLongMask(PyObject *vv) { PyLongObject *v; - unsigned long long x; Py_ssize_t i; int sign; @@ -1629,7 +1643,7 @@ _PyLong_AsUnsignedLongLongMask(PyObject *vv) } i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = _unroll_digits(v, &i); + unsigned long long x = unroll_digits_ulong(v, &i); while (--i >= 0) { x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; } @@ -1675,7 +1689,6 @@ PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow) { /* This version by Tim Peters */ PyLongObject *v; - unsigned long long x, prev; long long res; Py_ssize_t i; int sign; @@ -1717,15 +1730,14 @@ PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow) else { i = _PyLong_DigitCount(v); sign = _PyLong_NonCompactSign(v); - x = 0; + unsigned long long x = unroll_digits_ulong(v, &i); while (--i >= 0) { - prev = x; - x = (x << PyLong_SHIFT) + v->long_value.ob_digit[i]; - if ((x >> PyLong_SHIFT) != prev) { + if (x > ULLONG_MAX >> PyLong_SHIFT) { *overflow = sign; res = -1; goto exit; } + x = (x << PyLong_SHIFT) + v->long_value.ob_digit[i]; } /* Haven't lost any bits, but casting to long requires extra * care (see comment above). From 73e7b1b11e5bddaeaecfede2d139115df9797eea Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 14:12:29 +0200 Subject: [PATCH 08/13] review comments --- Objects/longobject.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 90af1d3bbfcf57..1c48a054f59a21 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -499,42 +499,46 @@ PyLong_FromDouble(double dval) #define PY_ABS_SSIZE_T_MIN (0-(size_t)PY_SSIZE_T_MIN) static inline unsigned long -unroll_digits_ulong(PyLongObject *v, Py_ssize_t *i) +unroll_digits_ulong(PyLongObject *v, Py_ssize_t *iptr) { + Py_ssize_t i = *iptr; digit *digits = v->long_value.ob_digit; - assert(*i >= 2); + assert(i >= 2); /* unroll 1 digit */ - --(*i); + --i; assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - unsigned long x = digits[*i]; + unsigned long x = digits[i]; #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) /* unroll another digit */ x <<= PyLong_SHIFT; - --(*i); - x |= digits[*i]; + --i; + x |= digits[i]; #endif + *iptr = i; return x; } static inline size_t -unroll_digits_size_t(PyLongObject *v, Py_ssize_t *i) +unroll_digits_size_t(PyLongObject *v, Py_ssize_t *iptr) { + Py_ssize_t i = *iptr; digit *digits = v->long_value.ob_digit; - assert(*i >= 2); + assert(i >= 2); /* unroll 1 digit */ - --(*i); + --i; assert(SIZE_MAX >= ((1UL << PyLong_SHIFT) - 1)); - size_t x = digits[*i]; + size_t x = digits[i]; #if ( (SIZE_MAX >> PyLong_SHIFT) >= ( ( 1 << PyLong_SHIFT) - 1) ) /* unroll another digit */ x <<= PyLong_SHIFT; - --(*i); - x |= digits[*i]; + --i; + x |= digits[i]; #endif + *iptr = i; return x; } From e89d66ce4b4a37455f08ce49ba7eced909804bd0 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 16:17:18 +0200 Subject: [PATCH 09/13] Apply suggestions from code review Co-authored-by: Victor Stinner --- Objects/longobject.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 1c48a054f59a21..f3c1f70cab814d 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -501,20 +501,22 @@ PyLong_FromDouble(double dval) static inline unsigned long unroll_digits_ulong(PyLongObject *v, Py_ssize_t *iptr) { + assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + Py_ssize_t i = *iptr; - digit *digits = v->long_value.ob_digit; assert(i >= 2); + /* unroll 1 digit */ --i; - assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); + digit *digits = v->long_value.ob_digit; unsigned long x = digits[i]; - #if ((ULONG_MAX >> PyLong_SHIFT)) >= ((1UL << PyLong_SHIFT) - 1) +#if (ULONG_MAX >> PyLong_SHIFT) >= ((1UL << PyLong_SHIFT) - 1) /* unroll another digit */ x <<= PyLong_SHIFT; --i; x |= digits[i]; - #endif +#endif *iptr = i; return x; @@ -523,12 +525,14 @@ unroll_digits_ulong(PyLongObject *v, Py_ssize_t *iptr) static inline size_t unroll_digits_size_t(PyLongObject *v, Py_ssize_t *iptr) { + assert(SIZE_MAX >= ((1UL << PyLong_SHIFT) - 1)); + Py_ssize_t i = *iptr; - digit *digits = v->long_value.ob_digit; assert(i >= 2); + /* unroll 1 digit */ --i; - assert(SIZE_MAX >= ((1UL << PyLong_SHIFT) - 1)); + digit *digits = v->long_value.ob_digit; size_t x = digits[i]; #if ( (SIZE_MAX >> PyLong_SHIFT) >= ( ( 1 << PyLong_SHIFT) - 1) ) @@ -690,7 +694,7 @@ PyLong_AsSsize_t(PyObject *vv) { size_t x = unroll_digits_size_t(v, &i); while (--i >= 0) { - if (x > SIZE_MAX >> PyLong_SHIFT) { + if (x > (SIZE_MAX >> PyLong_SHIFT)) { goto overflow; } x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; @@ -752,7 +756,7 @@ PyLong_AsUnsignedLong(PyObject *vv) unsigned long x = unroll_digits_ulong(v, &i); while (--i >= 0) { - if (x > ULONG_MAX >> PyLong_SHIFT) { + if (x > (ULONG_MAX >> PyLong_SHIFT)) { goto overflow; } x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i]; @@ -796,7 +800,7 @@ PyLong_AsSize_t(PyObject *vv) size_t x = unroll_digits_size_t(v, &i); while (--i >= 0) { - if (x > SIZE_MAX >> PyLong_SHIFT) { + if (x > (SIZE_MAX >> PyLong_SHIFT)) { PyErr_SetString(PyExc_OverflowError, "Python int too large to convert to C size_t"); return (size_t) -1; From 7822211527241febb513f395070f770f26149a54 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 16:20:41 +0200 Subject: [PATCH 10/13] update news entry --- .../2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst index d7f42b870e605d..b5f6acd343a1e3 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst @@ -1 +1,4 @@ -Improve performance of ``PyLongObject`` conversion method ``PyLong_AsLongAndOverflow``. +Improve performance of ``PyLongObject`` conversion methods ``PyLong_AsLongAndOverflow``, +``PyLong_AsLongAndOverflow()``, ``PyLong_AsSsize_t()``, ``PyLong_AsUnsignedLong()``, ``PyLong_AsSize_t()``, +``PyLong_AsUnsignedLongMask()``, ``PyLong_AsUnsignedLongLongMask()``, ``PyLong_AsLongLongAndOverflow()`` up to 30%. + From f4793e2c2ea9317ef12f6296fee4b15ef83b3b9e Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 16:22:51 +0200 Subject: [PATCH 11/13] reword --- .../2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst index b5f6acd343a1e3..e6ead578cc4a1a 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst @@ -1,4 +1,5 @@ Improve performance of ``PyLongObject`` conversion methods ``PyLong_AsLongAndOverflow``, ``PyLong_AsLongAndOverflow()``, ``PyLong_AsSsize_t()``, ``PyLong_AsUnsignedLong()``, ``PyLong_AsSize_t()``, -``PyLong_AsUnsignedLongMask()``, ``PyLong_AsUnsignedLongLongMask()``, ``PyLong_AsLongLongAndOverflow()`` up to 30%. +``PyLong_AsUnsignedLongMask()``, ``PyLong_AsUnsignedLongLongMask()``, ``PyLong_AsLongLongAndOverflow()`` +for integers larger than 2**30 up to 30%. From 1fc8706809728c142ca190985f1e5f973e349a4c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 16:32:12 +0200 Subject: [PATCH 12/13] lint --- Objects/longobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/longobject.c b/Objects/longobject.c index 707bc42f52918f..6d5748e129ba30 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -530,7 +530,7 @@ static inline unsigned long unroll_digits_ulong(PyLongObject *v, Py_ssize_t *iptr) { assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1)); - + Py_ssize_t i = *iptr; assert(i >= 2); From 2d9d09834bc9ab9b64cc4b7ffcf763319642565a Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 24 Jun 2025 20:44:50 +0200 Subject: [PATCH 13/13] Apply suggestions from code review Co-authored-by: Victor Stinner --- .../2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst | 2 +- Objects/longobject.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst index e6ead578cc4a1a..74bd182c1990f8 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-06-17-08-37-45.gh-issue-82088.TgPvLg.rst @@ -1,4 +1,4 @@ -Improve performance of ``PyLongObject`` conversion methods ``PyLong_AsLongAndOverflow``, +Improve performance of ``PyLongObject`` conversion functions ``PyLong_AsLongAndOverflow()``, ``PyLong_AsSsize_t()``, ``PyLong_AsUnsignedLong()``, ``PyLong_AsSize_t()``, ``PyLong_AsUnsignedLongMask()``, ``PyLong_AsUnsignedLongLongMask()``, ``PyLong_AsLongLongAndOverflow()`` for integers larger than 2**30 up to 30%. diff --git a/Objects/longobject.c b/Objects/longobject.c index 6d5748e129ba30..581db10b54ab57 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -563,12 +563,12 @@ unroll_digits_size_t(PyLongObject *v, Py_ssize_t *iptr) digit *digits = v->long_value.ob_digit; size_t x = digits[i]; - #if ( (SIZE_MAX >> PyLong_SHIFT) >= ( ( 1 << PyLong_SHIFT) - 1) ) +#if (SIZE_MAX >> PyLong_SHIFT) >= ((1 << PyLong_SHIFT) - 1) /* unroll another digit */ x <<= PyLong_SHIFT; --i; x |= digits[i]; - #endif +#endif *iptr = i; return x;