mirror of https://github.com/python/cpython
Microbenchmarks show 2-2.5x improvement. Built-in 'divmod' function is now also ~10% faster. -m timeit -s "x=22331" "x//2;x//-3;x//4;x//5;x//-6;x//7;x//8;x//-99;x//100;" with patch: 0.321 without patch: 0.633 -m timeit -s "x=22331" "x%2;x%3;x%-4;x%5;x%6;x%-7;x%8;x%99;x%-100;" with patch: 0.224 without patch: 0.66 Big thanks to Serhiy Storchaka, Mark Dickinson and Victor Stinner for thorow code reviews and algorithms improvements.
This commit is contained in:
parent
2da89d70fc
commit
e0b23095ee
|
@ -689,6 +689,20 @@ class LongTest(unittest.TestCase):
|
||||||
self.assertRaises(OverflowError, int, float('-inf'))
|
self.assertRaises(OverflowError, int, float('-inf'))
|
||||||
self.assertRaises(ValueError, int, float('nan'))
|
self.assertRaises(ValueError, int, float('nan'))
|
||||||
|
|
||||||
|
def test_mod_division(self):
|
||||||
|
with self.assertRaises(ZeroDivisionError):
|
||||||
|
_ = 1 % 0
|
||||||
|
|
||||||
|
self.assertEqual(13 % 10, 3)
|
||||||
|
self.assertEqual(-13 % 10, 7)
|
||||||
|
self.assertEqual(13 % -10, -7)
|
||||||
|
self.assertEqual(-13 % -10, -3)
|
||||||
|
|
||||||
|
self.assertEqual(12 % 4, 0)
|
||||||
|
self.assertEqual(-12 % 4, 0)
|
||||||
|
self.assertEqual(12 % -4, 0)
|
||||||
|
self.assertEqual(-12 % -4, 0)
|
||||||
|
|
||||||
def test_true_division(self):
|
def test_true_division(self):
|
||||||
huge = 1 << 40000
|
huge = 1 << 40000
|
||||||
mhuge = -huge
|
mhuge = -huge
|
||||||
|
@ -723,6 +737,25 @@ class LongTest(unittest.TestCase):
|
||||||
for zero in ["huge / 0", "mhuge / 0"]:
|
for zero in ["huge / 0", "mhuge / 0"]:
|
||||||
self.assertRaises(ZeroDivisionError, eval, zero, namespace)
|
self.assertRaises(ZeroDivisionError, eval, zero, namespace)
|
||||||
|
|
||||||
|
def test_floordiv(self):
|
||||||
|
with self.assertRaises(ZeroDivisionError):
|
||||||
|
_ = 1 // 0
|
||||||
|
|
||||||
|
self.assertEqual(2 // 3, 0)
|
||||||
|
self.assertEqual(2 // -3, -1)
|
||||||
|
self.assertEqual(-2 // 3, -1)
|
||||||
|
self.assertEqual(-2 // -3, 0)
|
||||||
|
|
||||||
|
self.assertEqual(-11 // -3, 3)
|
||||||
|
self.assertEqual(-11 // 3, -4)
|
||||||
|
self.assertEqual(11 // -3, -4)
|
||||||
|
self.assertEqual(11 // 3, 3)
|
||||||
|
|
||||||
|
self.assertEqual(-12 // -3, 4)
|
||||||
|
self.assertEqual(-12 // 3, -4)
|
||||||
|
self.assertEqual(12 // -3, -4)
|
||||||
|
self.assertEqual(12 // 3, 4)
|
||||||
|
|
||||||
def check_truediv(self, a, b, skip_small=True):
|
def check_truediv(self, a, b, skip_small=True):
|
||||||
"""Verify that the result of a/b is correctly rounded, by
|
"""Verify that the result of a/b is correctly rounded, by
|
||||||
comparing it with a pure Python implementation of correctly
|
comparing it with a pure Python implementation of correctly
|
||||||
|
|
|
@ -176,6 +176,10 @@ Core and Builtins
|
||||||
|
|
||||||
- Issue #26288: Optimize PyLong_AsDouble.
|
- Issue #26288: Optimize PyLong_AsDouble.
|
||||||
|
|
||||||
|
- Issues #26289 and #26315: Optimize floor and modulo division for
|
||||||
|
single-digit longs. Microbenchmarks show 2-2.5x improvement. Built-in
|
||||||
|
'divmod' function is now also ~10% faster.
|
||||||
|
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -3502,6 +3502,52 @@ long_mul(PyLongObject *a, PyLongObject *b)
|
||||||
return (PyObject *)z;
|
return (PyObject *)z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fast modulo division for single-digit longs. */
|
||||||
|
static PyObject *
|
||||||
|
fast_mod(PyLongObject *a, PyLongObject *b)
|
||||||
|
{
|
||||||
|
sdigit left = a->ob_digit[0];
|
||||||
|
sdigit right = b->ob_digit[0];
|
||||||
|
sdigit mod;
|
||||||
|
|
||||||
|
assert(Py_ABS(Py_SIZE(a)) == 1);
|
||||||
|
assert(Py_ABS(Py_SIZE(b)) == 1);
|
||||||
|
|
||||||
|
if (Py_SIZE(a) == Py_SIZE(b)) {
|
||||||
|
/* 'a' and 'b' have the same sign. */
|
||||||
|
mod = left % right;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Either 'a' or 'b' is negative. */
|
||||||
|
mod = right - 1 - (left - 1) % right;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(mod * Py_SIZE(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fast floor division for single-digit longs. */
|
||||||
|
static PyObject *
|
||||||
|
fast_floor_div(PyLongObject *a, PyLongObject *b)
|
||||||
|
{
|
||||||
|
sdigit left = a->ob_digit[0];
|
||||||
|
sdigit right = b->ob_digit[0];
|
||||||
|
sdigit div;
|
||||||
|
|
||||||
|
assert(Py_ABS(Py_SIZE(a)) == 1);
|
||||||
|
assert(Py_ABS(Py_SIZE(b)) == 1);
|
||||||
|
|
||||||
|
if (Py_SIZE(a) == Py_SIZE(b)) {
|
||||||
|
/* 'a' and 'b' have the same sign. */
|
||||||
|
div = left / right;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Either 'a' or 'b' is negative. */
|
||||||
|
div = -1 - (left - 1) / right;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyLong_FromLong(div);
|
||||||
|
}
|
||||||
|
|
||||||
/* The / and % operators are now defined in terms of divmod().
|
/* The / and % operators are now defined in terms of divmod().
|
||||||
The expression a mod b has the value a - b*floor(a/b).
|
The expression a mod b has the value a - b*floor(a/b).
|
||||||
The long_divrem function gives the remainder after division of
|
The long_divrem function gives the remainder after division of
|
||||||
|
@ -3529,6 +3575,30 @@ l_divmod(PyLongObject *v, PyLongObject *w,
|
||||||
{
|
{
|
||||||
PyLongObject *div, *mod;
|
PyLongObject *div, *mod;
|
||||||
|
|
||||||
|
if (Py_ABS(Py_SIZE(v)) == 1 && Py_ABS(Py_SIZE(w)) == 1) {
|
||||||
|
/* Fast path for single-digit longs */
|
||||||
|
div = NULL;
|
||||||
|
if (pdiv != NULL) {
|
||||||
|
div = (PyLongObject *)fast_floor_div(v, w);
|
||||||
|
if (div == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pmod != NULL) {
|
||||||
|
mod = (PyLongObject *)fast_mod(v, w);
|
||||||
|
if (mod == NULL) {
|
||||||
|
Py_XDECREF(div);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*pmod = mod;
|
||||||
|
}
|
||||||
|
if (pdiv != NULL) {
|
||||||
|
/* We only want to set `*pdiv` when `*pmod` is
|
||||||
|
set successfully. */
|
||||||
|
*pdiv = div;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (long_divrem(v, w, &div, &mod) < 0)
|
if (long_divrem(v, w, &div, &mod) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if ((Py_SIZE(mod) < 0 && Py_SIZE(w) > 0) ||
|
if ((Py_SIZE(mod) < 0 && Py_SIZE(w) > 0) ||
|
||||||
|
@ -3573,6 +3643,11 @@ long_div(PyObject *a, PyObject *b)
|
||||||
PyLongObject *div;
|
PyLongObject *div;
|
||||||
|
|
||||||
CHECK_BINOP(a, b);
|
CHECK_BINOP(a, b);
|
||||||
|
|
||||||
|
if (Py_ABS(Py_SIZE(a)) == 1 && Py_ABS(Py_SIZE(b)) == 1) {
|
||||||
|
return fast_floor_div((PyLongObject*)a, (PyLongObject*)b);
|
||||||
|
}
|
||||||
|
|
||||||
if (l_divmod((PyLongObject*)a, (PyLongObject*)b, &div, NULL) < 0)
|
if (l_divmod((PyLongObject*)a, (PyLongObject*)b, &div, NULL) < 0)
|
||||||
div = NULL;
|
div = NULL;
|
||||||
return (PyObject *)div;
|
return (PyObject *)div;
|
||||||
|
@ -3848,6 +3923,10 @@ long_mod(PyObject *a, PyObject *b)
|
||||||
|
|
||||||
CHECK_BINOP(a, b);
|
CHECK_BINOP(a, b);
|
||||||
|
|
||||||
|
if (Py_ABS(Py_SIZE(a)) == 1 && Py_ABS(Py_SIZE(b)) == 1) {
|
||||||
|
return fast_mod((PyLongObject*)a, (PyLongObject*)b);
|
||||||
|
}
|
||||||
|
|
||||||
if (l_divmod((PyLongObject*)a, (PyLongObject*)b, NULL, &mod) < 0)
|
if (l_divmod((PyLongObject*)a, (PyLongObject*)b, NULL, &mod) < 0)
|
||||||
mod = NULL;
|
mod = NULL;
|
||||||
return (PyObject *)mod;
|
return (PyObject *)mod;
|
||||||
|
|
Loading…
Reference in New Issue