Skip to content

Commit d754f75

Browse files
eendebakptblurb-it[bot]vstinner
authored
gh-82088: Improve performance of PyLong_As*() for multi-digit ints (#135585)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 85bc89f commit d754f75

File tree

2 files changed

+80
-35
lines changed

2 files changed

+80
-35
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Improve performance of ``PyLongObject`` conversion functions
2+
``PyLong_AsLongAndOverflow()``, ``PyLong_AsSsize_t()``, ``PyLong_AsUnsignedLong()``, ``PyLong_AsSize_t()``,
3+
``PyLong_AsUnsignedLongMask()``, ``PyLong_AsUnsignedLongLongMask()``, ``PyLong_AsLongLongAndOverflow()``
4+
for integers larger than 2**30 up to 30%.
5+

Objects/longobject.c

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,54 @@ PyLong_FromDouble(double dval)
526526
#define PY_ABS_LONG_MIN (0-(unsigned long)LONG_MIN)
527527
#define PY_ABS_SSIZE_T_MIN (0-(size_t)PY_SSIZE_T_MIN)
528528

529+
static inline unsigned long
530+
unroll_digits_ulong(PyLongObject *v, Py_ssize_t *iptr)
531+
{
532+
assert(ULONG_MAX >= ((1UL << PyLong_SHIFT) - 1));
533+
534+
Py_ssize_t i = *iptr;
535+
assert(i >= 2);
536+
537+
/* unroll 1 digit */
538+
--i;
539+
digit *digits = v->long_value.ob_digit;
540+
unsigned long x = digits[i];
541+
542+
#if (ULONG_MAX >> PyLong_SHIFT) >= ((1UL << PyLong_SHIFT) - 1)
543+
/* unroll another digit */
544+
x <<= PyLong_SHIFT;
545+
--i;
546+
x |= digits[i];
547+
#endif
548+
549+
*iptr = i;
550+
return x;
551+
}
552+
553+
static inline size_t
554+
unroll_digits_size_t(PyLongObject *v, Py_ssize_t *iptr)
555+
{
556+
assert(SIZE_MAX >= ((1UL << PyLong_SHIFT) - 1));
557+
558+
Py_ssize_t i = *iptr;
559+
assert(i >= 2);
560+
561+
/* unroll 1 digit */
562+
--i;
563+
digit *digits = v->long_value.ob_digit;
564+
size_t x = digits[i];
565+
566+
#if (SIZE_MAX >> PyLong_SHIFT) >= ((1 << PyLong_SHIFT) - 1)
567+
/* unroll another digit */
568+
x <<= PyLong_SHIFT;
569+
--i;
570+
x |= digits[i];
571+
#endif
572+
573+
*iptr = i;
574+
return x;
575+
}
576+
529577
/* Get a C long int from an int object or any object that has an __index__
530578
method.
531579
@@ -535,13 +583,11 @@ PyLong_FromDouble(double dval)
535583
For other errors (e.g., TypeError), return -1 and set an error condition.
536584
In this case *overflow will be 0.
537585
*/
538-
539586
long
540587
PyLong_AsLongAndOverflow(PyObject *vv, int *overflow)
541588
{
542-
/* This version by Tim Peters */
589+
/* This version originally by Tim Peters */
543590
PyLongObject *v;
544-
unsigned long x, prev;
545591
long res;
546592
Py_ssize_t i;
547593
int sign;
@@ -584,14 +630,14 @@ PyLong_AsLongAndOverflow(PyObject *vv, int *overflow)
584630
res = -1;
585631
i = _PyLong_DigitCount(v);
586632
sign = _PyLong_NonCompactSign(v);
587-
x = 0;
633+
634+
unsigned long x = unroll_digits_ulong(v, &i);
588635
while (--i >= 0) {
589-
prev = x;
590-
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
591-
if ((x >> PyLong_SHIFT) != prev) {
636+
if (x > (ULONG_MAX >> PyLong_SHIFT)) {
592637
*overflow = sign;
593638
goto exit;
594639
}
640+
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
595641
}
596642
/* Haven't lost any bits, but casting to long requires extra
597643
* care (see comment above).
@@ -655,7 +701,6 @@ PyLong_AsInt(PyObject *obj)
655701
Py_ssize_t
656702
PyLong_AsSsize_t(PyObject *vv) {
657703
PyLongObject *v;
658-
size_t x, prev;
659704
Py_ssize_t i;
660705
int sign;
661706

@@ -674,12 +719,13 @@ PyLong_AsSsize_t(PyObject *vv) {
674719
}
675720
i = _PyLong_DigitCount(v);
676721
sign = _PyLong_NonCompactSign(v);
677-
x = 0;
722+
723+
size_t x = unroll_digits_size_t(v, &i);
678724
while (--i >= 0) {
679-
prev = x;
680-
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
681-
if ((x >> PyLong_SHIFT) != prev)
725+
if (x > (SIZE_MAX >> PyLong_SHIFT)) {
682726
goto overflow;
727+
}
728+
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
683729
}
684730
/* Haven't lost any bits, but casting to a signed type requires
685731
* extra care (see comment above).
@@ -705,7 +751,6 @@ unsigned long
705751
PyLong_AsUnsignedLong(PyObject *vv)
706752
{
707753
PyLongObject *v;
708-
unsigned long x, prev;
709754
Py_ssize_t i;
710755

711756
if (vv == NULL) {
@@ -736,13 +781,13 @@ PyLong_AsUnsignedLong(PyObject *vv)
736781
return (unsigned long) -1;
737782
}
738783
i = _PyLong_DigitCount(v);
739-
x = 0;
784+
785+
unsigned long x = unroll_digits_ulong(v, &i);
740786
while (--i >= 0) {
741-
prev = x;
742-
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
743-
if ((x >> PyLong_SHIFT) != prev) {
787+
if (x > (ULONG_MAX >> PyLong_SHIFT)) {
744788
goto overflow;
745789
}
790+
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
746791
}
747792
return x;
748793
overflow:
@@ -759,7 +804,6 @@ size_t
759804
PyLong_AsSize_t(PyObject *vv)
760805
{
761806
PyLongObject *v;
762-
size_t x, prev;
763807
Py_ssize_t i;
764808

765809
if (vv == NULL) {
@@ -781,16 +825,16 @@ PyLong_AsSize_t(PyObject *vv)
781825
return (size_t) -1;
782826
}
783827
i = _PyLong_DigitCount(v);
784-
x = 0;
828+
829+
size_t x = unroll_digits_size_t(v, &i);
785830
while (--i >= 0) {
786-
prev = x;
787-
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
788-
if ((x >> PyLong_SHIFT) != prev) {
789-
PyErr_SetString(PyExc_OverflowError,
790-
"Python int too large to convert to C size_t");
791-
return (size_t) -1;
831+
if (x > (SIZE_MAX >> PyLong_SHIFT)) {
832+
PyErr_SetString(PyExc_OverflowError,
833+
"Python int too large to convert to C size_t");
834+
return (size_t) -1;
835+
}
836+
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
792837
}
793-
}
794838
return x;
795839
}
796840

@@ -801,7 +845,6 @@ static unsigned long
801845
_PyLong_AsUnsignedLongMask(PyObject *vv)
802846
{
803847
PyLongObject *v;
804-
unsigned long x;
805848
Py_ssize_t i;
806849

807850
if (vv == NULL || !PyLong_Check(vv)) {
@@ -818,7 +861,7 @@ _PyLong_AsUnsignedLongMask(PyObject *vv)
818861
}
819862
i = _PyLong_DigitCount(v);
820863
int sign = _PyLong_NonCompactSign(v);
821-
x = 0;
864+
unsigned long x = unroll_digits_ulong(v, &i);
822865
while (--i >= 0) {
823866
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
824867
}
@@ -1619,7 +1662,6 @@ static unsigned long long
16191662
_PyLong_AsUnsignedLongLongMask(PyObject *vv)
16201663
{
16211664
PyLongObject *v;
1622-
unsigned long long x;
16231665
Py_ssize_t i;
16241666
int sign;
16251667

@@ -1637,7 +1679,7 @@ _PyLong_AsUnsignedLongLongMask(PyObject *vv)
16371679
}
16381680
i = _PyLong_DigitCount(v);
16391681
sign = _PyLong_NonCompactSign(v);
1640-
x = 0;
1682+
unsigned long long x = unroll_digits_ulong(v, &i);
16411683
while (--i >= 0) {
16421684
x = (x << PyLong_SHIFT) | v->long_value.ob_digit[i];
16431685
}
@@ -1683,7 +1725,6 @@ PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow)
16831725
{
16841726
/* This version by Tim Peters */
16851727
PyLongObject *v;
1686-
unsigned long long x, prev;
16871728
long long res;
16881729
Py_ssize_t i;
16891730
int sign;
@@ -1725,15 +1766,14 @@ PyLong_AsLongLongAndOverflow(PyObject *vv, int *overflow)
17251766
else {
17261767
i = _PyLong_DigitCount(v);
17271768
sign = _PyLong_NonCompactSign(v);
1728-
x = 0;
1769+
unsigned long long x = unroll_digits_ulong(v, &i);
17291770
while (--i >= 0) {
1730-
prev = x;
1731-
x = (x << PyLong_SHIFT) + v->long_value.ob_digit[i];
1732-
if ((x >> PyLong_SHIFT) != prev) {
1771+
if (x > ULLONG_MAX >> PyLong_SHIFT) {
17331772
*overflow = sign;
17341773
res = -1;
17351774
goto exit;
17361775
}
1776+
x = (x << PyLong_SHIFT) + v->long_value.ob_digit[i];
17371777
}
17381778
/* Haven't lost any bits, but casting to long requires extra
17391779
* care (see comment above).

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy