gh-90716: add _pylong.py module (#96673)

Add Python implementations of certain longobject.c functions. These use
asymptotically faster algorithms that can be used for operations on
integers with many digits. In those cases, the performance overhead of
the Python implementation is not significant since the asymptotic
behavior is what dominates runtime. Functions provided by this module
should be considered private and not part of any public API.

Co-author: Tim Peters <tim.peters@gmail.com>
Co-author: Mark Dickinson <dickinsm@gmail.com>
Co-author: Bjorn Martinsson
This commit is contained in:
Neil Schemenauer 2022-10-25 22:00:50 -07:00 committed by GitHub
parent 5d30544485
commit de6981680b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 572 additions and 0 deletions

View file

@ -39,6 +39,9 @@ medium_value(PyLongObject *x)
#define _MAX_STR_DIGITS_ERROR_FMT_TO_INT "Exceeds the limit (%d) for integer string conversion: value has %zd digits; use sys.set_int_max_str_digits() to increase the limit"
#define _MAX_STR_DIGITS_ERROR_FMT_TO_STR "Exceeds the limit (%d) for integer string conversion; use sys.set_int_max_str_digits() to increase the limit"
/* If defined, use algorithms from the _pylong.py module */
#define WITH_PYLONG_MODULE 1
static inline void
_Py_DECREF_INT(PyLongObject *op)
{
@ -1732,6 +1735,69 @@ rem1(PyLongObject *a, digit n)
);
}
#ifdef WITH_PYLONG_MODULE
/* asymptotically faster long_to_decimal_string, using _pylong.py */
static int
pylong_int_to_decimal_string(PyObject *aa,
PyObject **p_output,
_PyUnicodeWriter *writer,
_PyBytesWriter *bytes_writer,
char **bytes_str)
{
PyObject *s = NULL;
PyObject *mod = PyImport_ImportModule("_pylong");
if (mod == NULL) {
return -1;
}
s = PyObject_CallMethod(mod, "int_to_decimal_string", "O", aa);
if (s == NULL) {
goto error;
}
assert(PyUnicode_Check(s));
if (writer) {
Py_ssize_t size = PyUnicode_GET_LENGTH(s);
if (_PyUnicodeWriter_Prepare(writer, size, '9') == -1) {
goto error;
}
if (_PyUnicodeWriter_WriteStr(writer, s) < 0) {
goto error;
}
goto success;
}
else if (bytes_writer) {
Py_ssize_t size = PyUnicode_GET_LENGTH(s);
const void *data = PyUnicode_DATA(s);
int kind = PyUnicode_KIND(s);
*bytes_str = _PyBytesWriter_Prepare(bytes_writer, *bytes_str, size);
if (*bytes_str == NULL) {
goto error;
}
char *p = *bytes_str;
for (Py_ssize_t i=0; i < size; i++) {
Py_UCS4 ch = PyUnicode_READ(kind, data, i);
*p++ = (char) ch;
}
(*bytes_str) = p;
goto success;
}
else {
*p_output = (PyObject *)s;
Py_INCREF(s);
goto success;
}
error:
Py_DECREF(mod);
Py_XDECREF(s);
return -1;
success:
Py_DECREF(mod);
Py_DECREF(s);
return 0;
}
#endif /* WITH_PYLONG_MODULE */
/* Convert an integer to a base 10 string. Returns a new non-shared
string. (Return value is non-shared so that callers can modify the
returned value if necessary.) */
@ -1776,6 +1842,17 @@ long_to_decimal_string_internal(PyObject *aa,
}
}
#if WITH_PYLONG_MODULE
if (size_a > 1000) {
/* Switch to _pylong.int_to_decimal_string(). */
return pylong_int_to_decimal_string(aa,
p_output,
writer,
bytes_writer,
bytes_str);
}
#endif
/* quick and dirty upper bound for the number of digits
required to express a in base _PyLong_DECIMAL_BASE:
@ -2272,6 +2349,39 @@ long_from_binary_base(const char *start, const char *end, Py_ssize_t digits, int
return 0;
}
static PyObject *long_neg(PyLongObject *v);
#ifdef WITH_PYLONG_MODULE
/* asymptotically faster str-to-long conversion for base 10, using _pylong.py */
static int
pylong_int_from_string(const char *start, const char *end, PyLongObject **res)
{
PyObject *mod = PyImport_ImportModule("_pylong");
if (mod == NULL) {
goto error;
}
PyObject *s = PyUnicode_FromStringAndSize(start, end-start);
if (s == NULL) {
goto error;
}
PyObject *result = PyObject_CallMethod(mod, "int_from_string", "O", s);
Py_DECREF(s);
Py_DECREF(mod);
if (result == NULL) {
goto error;
}
if (!PyLong_Check(result)) {
PyErr_SetString(PyExc_TypeError, "an integer is required");
goto error;
}
*res = (PyLongObject *)result;
return 0;
error:
*res = NULL;
return 0;
}
#endif /* WITH_PYLONG_MODULE */
/***
long_from_non_binary_base: parameters and return values are the same as
long_from_binary_base.
@ -2586,6 +2696,12 @@ long_from_string_base(const char **str, int base, PyLongObject **res)
return 0;
}
}
#if WITH_PYLONG_MODULE
if (digits > 6000 && base == 10) {
/* Switch to _pylong.int_from_string() */
return pylong_int_from_string(start, end, res);
}
#endif
/* Use the quadratic algorithm for non binary bases. */
return long_from_non_binary_base(start, end, digits, base, res);
}
@ -3913,6 +4029,48 @@ fast_floor_div(PyLongObject *a, PyLongObject *b)
return PyLong_FromLong(div);
}
#ifdef WITH_PYLONG_MODULE
/* asymptotically faster divmod, using _pylong.py */
static int
pylong_int_divmod(PyLongObject *v, PyLongObject *w,
PyLongObject **pdiv, PyLongObject **pmod)
{
PyObject *mod = PyImport_ImportModule("_pylong");
if (mod == NULL) {
return -1;
}
PyObject *result = PyObject_CallMethod(mod, "int_divmod", "OO", v, w);
Py_DECREF(mod);
if (result == NULL) {
return -1;
}
if (!PyTuple_Check(result)) {
Py_DECREF(result);
PyErr_SetString(PyExc_ValueError,
"tuple is required from int_divmod()");
return -1;
}
PyObject *q = PyTuple_GET_ITEM(result, 0);
PyObject *r = PyTuple_GET_ITEM(result, 1);
if (!PyLong_Check(q) || !PyLong_Check(r)) {
Py_DECREF(result);
PyErr_SetString(PyExc_ValueError,
"tuple of int is required from int_divmod()");
return -1;
}
if (pdiv != NULL) {
Py_INCREF(q);
*pdiv = (PyLongObject *)q;
}
if (pmod != NULL) {
Py_INCREF(r);
*pmod = (PyLongObject *)r;
}
Py_DECREF(result);
return 0;
}
#endif /* WITH_PYLONG_MODULE */
/* The / and % operators are now defined in terms of divmod().
The expression a mod b has the value a - b*floor(a/b).
The long_divrem function gives the remainder after division of
@ -3964,6 +4122,18 @@ l_divmod(PyLongObject *v, PyLongObject *w,
}
return 0;
}
#if WITH_PYLONG_MODULE
Py_ssize_t size_v = Py_ABS(Py_SIZE(v)); /* digits in numerator */
Py_ssize_t size_w = Py_ABS(Py_SIZE(w)); /* digits in denominator */
if (size_w > 300 && (size_v - size_w) > 150) {
/* Switch to _pylong.int_divmod(). If the quotient is small then
"schoolbook" division is linear-time so don't use in that case.
These limits are empirically determined and should be slightly
conservative so that _pylong is used in cases it is likely
to be faster. See Tools/scripts/divmod_threshold.py. */
return pylong_int_divmod(v, w, pdiv, pmod);
}
#endif
if (long_divrem(v, w, &div, &mod) < 0)
return -1;
if ((Py_SIZE(mod) < 0 && Py_SIZE(w) > 0) ||