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 <[email protected]>
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)