Issues #26289 and #26315: Optimize floor/modulo div for single-digit longs

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:
Yury Selivanov 2016-02-11 10:26:27 -05:00
parent 2da89d70fc
commit e0b23095ee
3 changed files with 116 additions and 0 deletions

View File

@ -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

View File

@ -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
------- -------

View File

@ -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;