Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ PyAPI_DATA(PyObject*) _PyLong_Lshift(PyObject *, int64_t);
PyAPI_FUNC(_PyStackRef) _PyCompactLong_Add(PyLongObject *left, PyLongObject *right);
PyAPI_FUNC(_PyStackRef) _PyCompactLong_Multiply(PyLongObject *left, PyLongObject *right);
PyAPI_FUNC(_PyStackRef) _PyCompactLong_Subtract(PyLongObject *left, PyLongObject *right);
PyAPI_FUNC(_PyStackRef) _PyLong_SubtractShrink(PyLongObject *left, PyLongObject *right);

// Export for 'binascii' shared extension.
PyAPI_DATA(unsigned char) _PyLong_DigitValue[256];
Expand Down Expand Up @@ -346,6 +347,28 @@ _PyLong_CheckExactAndCompact(PyObject *op)
return PyLong_CheckExact(op) && _PyLong_IsCompact((const PyLongObject *)op);
}

static inline bool
_PyLong_IsShrinkSubtractPair(const PyLongObject *left, const PyLongObject *right)
{
#if PyLong_SHIFT == 30
if (_PyLong_IsCompact((PyLongObject *)left) ||
_PyLong_IsCompact((PyLongObject *)right) ||
!_PyLong_SameSign(left, right)) {
return false;
}
Py_ssize_t left_size = _PyLong_DigitCount(left);
if (left_size != _PyLong_DigitCount(right) ||
left_size < 2 ||
left_size > 3) {
return false;
}
return left->long_value.ob_digit[left_size - 1] ==
right->long_value.ob_digit[left_size - 1];
#else
return false;
#endif
}

#ifdef __cplusplus
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_opcode_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions Include/internal/pycore_uop_ids.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 4 additions & 8 deletions Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4455,6 +4455,22 @@ def testfunc(n):
self.assertIn("_POP_TOP_NOP", uops)
self.assertLessEqual(count_ops(ex, "_POP_TOP"), 2)

def test_int_subtract_shrink_specialization(self):
def testfunc(args):
a, b, n = args
res = 0
for _ in range(n):
res = a - b
return res

res, ex = self._run_with_optimizer(
testfunc, (10_000_000_001, 10_000_000_000, TIER2_THRESHOLD)
)
self.assertEqual(res, 1)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_SUBTRACT_INT", uops)

def test_int_mul_op_refcount_elimination(self):
def testfunc(n):
c = 1
Expand Down
19 changes: 19 additions & 0 deletions Lib/test/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,25 @@ def test_small_ints(self):
self.assertIs(i - i, 0)
self.assertIs(0 * i, 0)

@support.cpython_only
def test_int64_boundary_sub(self):
INT64_MAX = (1 << 63) - 1
INT64_MIN = -(1 << 63)

self.assertEqual(INT64_MIN - 0, INT64_MIN)
self.assertEqual(INT64_MIN - (-1), INT64_MIN + 1)

self.assertEqual(INT64_MAX - 0, INT64_MAX)
self.assertEqual(INT64_MAX - 1, INT64_MAX - 1)

self.assertEqual(INT64_MIN - 1, INT64_MIN - 1)
self.assertEqual(INT64_MAX - (-1), 1 << 63)

beyond_max = INT64_MAX + 2
beyond_min = INT64_MIN - 2
self.assertEqual(beyond_max - 1, INT64_MAX + 1)
self.assertEqual(beyond_min - (-1), INT64_MIN - 1)

def test_bit_length(self):
tiny = 1e-10
for x in range(-65000, 65000):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve performance of binary ``int`` subtraction for shrinking wide exact
integers.
53 changes: 31 additions & 22 deletions Modules/_testinternalcapi/test_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 66 additions & 0 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3916,6 +3916,72 @@ _PyCompactLong_Subtract(PyLongObject *a, PyLongObject *b)
return medium_from_stwodigits(v);
}

static inline _PyStackRef
shrink_subtract_result(int sign, const digit *digits, Py_ssize_t size)
{
while (size > 0 && digits[size - 1] == 0) {
--size;
}
if (size == 0) {
return PyStackRef_FromPyObjectBorrow(_PyLong_GetZero());
}
if (size == 1) {
stwodigits value = (stwodigits)digits[0];
if (sign < 0) {
value = -value;
}
return medium_from_stwodigits(value);
}
PyLongObject *result = PyObject_Malloc(
offsetof(PyLongObject, long_value.ob_digit) + size * sizeof(digit));
if (result == NULL) {
return PyStackRef_NULL;
}
_PyObject_Init((PyObject *)result, &PyLong_Type);
_PyLong_InitTag(result);
_PyLong_SetSignAndDigitCount(result, sign, size);
memcpy(result->long_value.ob_digit, digits, size * sizeof(digit));
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}

_PyStackRef
_PyLong_SubtractShrink(PyLongObject *left, PyLongObject *right)
{
if (!_PyLong_IsShrinkSubtractPair(left, right)) {
return PyStackRef_NULL;
}
Py_ssize_t size = _PyLong_DigitCount(left);
assert(size >= 2 && size <= 3);

int sign = _PyLong_IsNegative(left) ? -1 : 1;
int cmp = 0;
for (Py_ssize_t i = size - 2; i >= 0; i--) {
digit left_digit = left->long_value.ob_digit[i];
digit right_digit = right->long_value.ob_digit[i];
if (left_digit != right_digit) {
cmp = left_digit > right_digit ? 1 : -1;
break;
}
}
if (cmp == 0) {
return PyStackRef_FromPyObjectBorrow(_PyLong_GetZero());
}

PyLongObject *larger = cmp > 0 ? left : right;
PyLongObject *smaller = cmp > 0 ? right : left;
digit out[3];
twodigits borrow = 0;
for (Py_ssize_t i = 0; i < size; i++) {
twodigits lhs = (twodigits)larger->long_value.ob_digit[i];
twodigits rhs = (twodigits)smaller->long_value.ob_digit[i];
twodigits diff = lhs - rhs - borrow;
out[i] = (digit)(diff & PyLong_MASK);
borrow = lhs < rhs + borrow;
}
assert(borrow == 0);
return shrink_subtract_result(cmp > 0 ? sign : -sign, out, size);
}

static PyObject *
long_sub_method(PyObject *a, PyObject *b)
{
Expand Down
20 changes: 15 additions & 5 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -688,12 +688,22 @@ dummy_func(
pure op(_BINARY_OP_SUBTRACT_INT, (left, right -- res, l, r)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));
EXIT_IF(!PyLong_CheckExact(left_o));
EXIT_IF(!PyLong_CheckExact(right_o));
bool both_compact = _PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o);
bool shrink_pair = false;
if (!both_compact) {
shrink_pair = _PyLong_IsShrinkSubtractPair((PyLongObject *)left_o, (PyLongObject *)right_o);
}
EXIT_IF(!(both_compact || shrink_pair));

STAT_INC(BINARY_OP, hit);
res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
if (both_compact) {
res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
}
else {
res = _PyLong_SubtractShrink((PyLongObject *)left_o, (PyLongObject *)right_o);
}
EXIT_IF(PyStackRef_IsNull(res));
l = left;
r = right;
Expand All @@ -707,7 +717,7 @@ dummy_func(
_GUARD_TOS_INT + _GUARD_NOS_INT + unused/5 + _BINARY_OP_ADD_INT + _POP_TOP_INT + _POP_TOP_INT;

macro(BINARY_OP_SUBTRACT_INT) =
_GUARD_TOS_INT + _GUARD_NOS_INT + unused/5 + _BINARY_OP_SUBTRACT_INT + _POP_TOP_INT + _POP_TOP_INT;
unused/5 + _BINARY_OP_SUBTRACT_INT + _POP_TOP_INT + _POP_TOP_INT;

// Inplace compact int ops: mutate the uniquely-referenced operand
// when possible. The op handles decref of TARGET internally so
Expand Down
Loading
Loading