Skip to content

Commit 993ee82

Browse files
eendebakptmdickinsonclaude
committed
gh-100687: Reduce frequency of overallocation in x_add()
x_add() allocated a digit for the carry out of the top digit on every call. In most cases that digit is zero and long_normalize() drops it from the digit count, but the allocation keeps its size, so the result uses more memory than its value needs for as long as it lives. Allocate the carry digit only when the top digits of the operands sum to at least PyLong_MASK, the only case in which a carry out of the top digit is possible. When no carry digit was allocated the top digit of the result is at least the top digit of the larger operand, so the result is already normalized and is returned directly. This applies to same-sign additions and opposite-sign subtractions, which go through x_add(); x_sub() is unchanged. Extends PR 100688 by Mark Dickinson, which contributed the size test and the tests. Co-authored-by: Mark Dickinson <dickinsm@gmail.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 9777e8a commit 993ee82

3 files changed

Lines changed: 31 additions & 3 deletions

File tree

‎Lib/test/test_long.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,21 @@ class MyInt(int):
16981698
# GH-117195 -- This shouldn't crash
16991699
object.__sizeof__(1)
17001700

1701+
def test_long_add_overallocate(self):
1702+
# see gh-100687
1703+
x = (MASK//2) * (MASK+1)
1704+
x2 = (MASK//2 + 1) * (MASK+1)
1705+
z = x + x2
1706+
self.assertEqual(x + x2, MASK * (MASK + 1))
1707+
1708+
def test_karatsuba_single_digit_parts(self):
1709+
# gh-100687: k_mul() adds the halves of its operands with x_add(),
1710+
# and those halves can be single digits.
1711+
a = 1 + (1 << (SHIFT * 70))
1712+
b = 1 << (SHIFT * 139)
1713+
self.assertEqual(a * b, (1 << (SHIFT * 139)) + (1 << (SHIFT * 209)))
1714+
self.assertEqual(b * a, a * b)
1715+
17011716
def test_hash(self):
17021717
# gh-136599
17031718
self.assertEqual(hash(-1), -2)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reduce frequency of overallocation in some cases of multidigit integer
2+
additions and subtractions.

‎Objects/longobject.c‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3760,7 +3760,13 @@ x_add(PyLongObject *a, PyLongObject *b)
37603760
size_a = size_b;
37613761
size_b = size_temp; }
37623762
}
3763-
z = long_alloc(size_a+1);
3763+
assert(size_a >= 1);
3764+
/* A carry out of the top digit is only possible if the top digits sum
3765+
to at least PyLong_MASK; only then allocate a digit for it. */
3766+
digit top_sum = a->long_value.ob_digit[size_a - 1]
3767+
+ (size_b == size_a ? b->long_value.ob_digit[size_b - 1] : (digit)0);
3768+
int extra_digit = top_sum >= PyLong_MASK;
3769+
z = long_alloc(size_a + extra_digit);
37643770
if (z == NULL)
37653771
return NULL;
37663772
for (i = 0; i < size_b; ++i) {
@@ -3773,8 +3779,13 @@ x_add(PyLongObject *a, PyLongObject *b)
37733779
z->long_value.ob_digit[i] = carry & PyLong_MASK;
37743780
carry >>= PyLong_SHIFT;
37753781
}
3776-
z->long_value.ob_digit[i] = carry;
3777-
return long_normalize(z);
3782+
if (extra_digit) {
3783+
z->long_value.ob_digit[i] = carry;
3784+
return long_normalize(z);
3785+
}
3786+
assert(carry == 0);
3787+
assert(z->long_value.ob_digit[i - 1] != 0);
3788+
return z;
37783789
}
37793790

37803791
/* Subtract the absolute values of two integers. */

0 commit comments

Comments
 (0)