From e87b87dc05a0d9f9977756aa981d779f015db839 Mon Sep 17 00:00:00 2001 From: David Einstein Date: Sun, 20 Aug 2023 13:00:38 -0400 Subject: [PATCH 1/4] Directly initialize fmpz from a python int. Mimics the code from gmpy2 and so depends on undocumented internals of PyLong. --- src/flint/_flint.pxd | 19 ++++++++++++++++++- src/flint/fmpz.pyx | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/flint/_flint.pxd b/src/flint/_flint.pxd index 166e14a6..bc979f2d 100644 --- a/src/flint/_flint.pxd +++ b/src/flint/_flint.pxd @@ -2,6 +2,9 @@ # # Define the contents of the Python, GMP, Flint and Arb headers. +from libc.stdint cimport uint32_t +from libc.stdint cimport int32_t + cdef extern from "Python.h": ctypedef void PyObject ctypedef void PyTypeObject @@ -14,6 +17,14 @@ cdef extern from "Python.h": Py_ssize_t PyList_GET_SIZE(PyObject *list) long PyLong_AsLongAndOverflow(PyObject *pylong, int *overflow) long long PyLong_AsLongLongAndOverflow(PyObject *pylong, int *overflow) + int PyLong_SHIFT + int PYLONG_BITS_IN_DIGIT + Py_ssize_t Py_SIZE(PyObject *io) + ctypedef uint32_t digit + ctypedef int32_t sdigit + ctypedef struct PyLongObject: + digit *ob_digit + DEF FMPZ_UNKNOWN = 0 DEF FMPZ_REF = 1 @@ -37,6 +48,12 @@ cdef extern from "gmp.h": ctypedef mp_limb_t* mp_ptr ctypedef mp_limb_t* mp_srcptr ctypedef unsigned long mp_bitcnt_t + ctypedef struct mpz_t: + pass + void mpz_init(mpz_t) + void mpz_import(mpz_t val, size_t count, int order, size_t size, int endian, size_t nails, const void * op) + void mpz_clear(mpz_t val) + void mpz_neg(mpz_t rop, mpz_t op) cdef extern from "flint/fmpz.h": ctypedef long slong @@ -225,7 +242,7 @@ cdef extern from "flint/fmpz.h": void fmpz_set_si(fmpz_t f, long val) void fmpz_set_ui(fmpz_t f, ulong val) #void fmpz_get_mpz(mpz_t x, fmpz_t f) - #void fmpz_set_mpz(fmpz_t f, mpz_t x) + void fmpz_set_mpz(fmpz_t f, mpz_t x) int fmpz_set_str(fmpz_t f, char * str, int b) int fmpz_abs_fits_ui( fmpz_t f) void fmpz_zero(fmpz_t f) diff --git a/src/flint/fmpz.pyx b/src/flint/fmpz.pyx index 5900aa0e..7eb7d5c5 100644 --- a/src/flint/fmpz.pyx +++ b/src/flint/fmpz.pyx @@ -1,12 +1,37 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): - cdef int overflow - cdef slong longval - longval = pylong_as_slong(obj, &overflow) - if overflow: - s = "%x" % obj - fmpz_set_str(x, chars_from_str(s), 16) + cdef Py_ssize_t length + cdef mpz_t mpz_val + cdef bint negative + # cdef int overflow + # cdef slong longval + length = Py_SIZE(obj) + if length == -1: + fmpz_set_si(x, -(((obj).ob_digit[0]))) + elif length == 0: + fmpz_zero(x) + elif length == 1: + fmpz_set_si(x, (obj).ob_digit[0]) else: - fmpz_set_si(x, longval) + # We probably want to use _fmpz_promote here to avoid the copy + mpz_init(mpz_val) + negative = False + if length < 0: + negative = True + length = -length + mpz_import(mpz_val, length, -1, sizeof((obj).ob_digit[0]),0, + sizeof((obj).ob_digit[0])*8 - PyLong_SHIFT, (obj).ob_digit) + if negative: + mpz_neg(mpz_val, mpz_val) + fmpz_set_mpz(x, mpz_val) + mpz_clear(mpz_val) + + + # longval = pylong_as_slong(obj, &overflow) + # if overflow: + # s = "%x" % obj + # fmpz_set_str(x, chars_from_str(s), 16) + # else: + # fmpz_set_si(x, longval) cdef inline int fmpz_set_python(fmpz_t x, obj): if PY_MAJOR_VERSION < 3 and PyInt_Check(obj): From c02a4c6ae30288e5f8fa92964fd445f00194c805 Mon Sep 17 00:00:00 2001 From: David Einstein Date: Mon, 21 Aug 2023 09:54:14 -0400 Subject: [PATCH 2/4] Changed fmpz_set_pylong to use _fmpz_promote and demote No longer allocating a separate fmpz object so should be faster. --- src/flint/_flint.pxd | 5 ++++- src/flint/fmpz.pyx | 18 +++--------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/flint/_flint.pxd b/src/flint/_flint.pxd index bc979f2d..c467c38e 100644 --- a/src/flint/_flint.pxd +++ b/src/flint/_flint.pxd @@ -48,8 +48,9 @@ cdef extern from "gmp.h": ctypedef mp_limb_t* mp_ptr ctypedef mp_limb_t* mp_srcptr ctypedef unsigned long mp_bitcnt_t - ctypedef struct mpz_t: + ctypedef struct __mpz_struct: pass + ctypedef __mpz_struct mpz_t[1] void mpz_init(mpz_t) void mpz_import(mpz_t val, size_t count, int order, size_t size, int endian, size_t nails, const void * op) void mpz_clear(mpz_t val) @@ -332,6 +333,8 @@ cdef extern from "flint/fmpz.h": void fmpz_and(fmpz_t r, const fmpz_t a, const fmpz_t b) void fmpz_or(fmpz_t r, const fmpz_t a, const fmpz_t b) void fmpz_xor(fmpz_t r, const fmpz_t a, const fmpz_t b) + __mpz_struct *_fmpz_promote(fmpz_t f) + void _fmpz_demote_val(fmpz_t f) cdef extern from "flint/fmpz_factor.h": ctypedef struct fmpz_factor_struct: diff --git a/src/flint/fmpz.pyx b/src/flint/fmpz.pyx index 7eb7d5c5..7e7fa659 100644 --- a/src/flint/fmpz.pyx +++ b/src/flint/fmpz.pyx @@ -1,9 +1,7 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): cdef Py_ssize_t length - cdef mpz_t mpz_val + cdef __mpz_struct* mpz_val cdef bint negative - # cdef int overflow - # cdef slong longval length = Py_SIZE(obj) if length == -1: fmpz_set_si(x, -(((obj).ob_digit[0]))) @@ -12,8 +10,7 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): elif length == 1: fmpz_set_si(x, (obj).ob_digit[0]) else: - # We probably want to use _fmpz_promote here to avoid the copy - mpz_init(mpz_val) + mpz_val = _fmpz_promote(x) negative = False if length < 0: negative = True @@ -22,16 +19,7 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): sizeof((obj).ob_digit[0])*8 - PyLong_SHIFT, (obj).ob_digit) if negative: mpz_neg(mpz_val, mpz_val) - fmpz_set_mpz(x, mpz_val) - mpz_clear(mpz_val) - - - # longval = pylong_as_slong(obj, &overflow) - # if overflow: - # s = "%x" % obj - # fmpz_set_str(x, chars_from_str(s), 16) - # else: - # fmpz_set_si(x, longval) + _fmpz_demote_val(x) cdef inline int fmpz_set_python(fmpz_t x, obj): if PY_MAJOR_VERSION < 3 and PyInt_Check(obj): From ec810d5730659ab589e7e654045de4161bf65d1f Mon Sep 17 00:00:00 2001 From: David Einstein Date: Mon, 21 Aug 2023 10:57:56 -0400 Subject: [PATCH 3/4] change fmpz_get_intlong to use mpz_export This uses the method from sagemath which is a bit simpler than that of gmpy2. It is missing a normalization step that is in gmpy2. I guess that the normalization step is not necessary. --- src/flint/_flint.pxd | 3 +++ src/flint/fmpz.pyx | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/flint/_flint.pxd b/src/flint/_flint.pxd index c467c38e..6b6845ab 100644 --- a/src/flint/_flint.pxd +++ b/src/flint/_flint.pxd @@ -55,6 +55,8 @@ cdef extern from "gmp.h": void mpz_import(mpz_t val, size_t count, int order, size_t size, int endian, size_t nails, const void * op) void mpz_clear(mpz_t val) void mpz_neg(mpz_t rop, mpz_t op) + size_t mpz_sizeinbase (const mpz_t op, int base) + void * mpz_export (void *rop, size_t *countp, int order, size_t size, int endian, size_t nails, const mpz_t op) cdef extern from "flint/fmpz.h": ctypedef long slong @@ -334,6 +336,7 @@ cdef extern from "flint/fmpz.h": void fmpz_or(fmpz_t r, const fmpz_t a, const fmpz_t b) void fmpz_xor(fmpz_t r, const fmpz_t a, const fmpz_t b) __mpz_struct *_fmpz_promote(fmpz_t f) + __mpz_struct *_fmpz_promote_val(fmpz_t f) void _fmpz_demote_val(fmpz_t f) cdef extern from "flint/fmpz_factor.h": diff --git a/src/flint/fmpz.pyx b/src/flint/fmpz.pyx index 7e7fa659..da3a8769 100644 --- a/src/flint/fmpz.pyx +++ b/src/flint/fmpz.pyx @@ -1,8 +1,27 @@ +from cpython.object cimport Py_SIZE +from cpython.int cimport PyInt_FromLong +from cpython.long cimport PyLong_FromLong +from cpython.longintrepr cimport _PyLong_New, py_long, digit, PyLong_SHIFT + +cdef extern from *: + """ + /* Compatibility for python 3.8, can be removed later */ + #if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) + static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) + { ob->ob_size = size; } + #define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) + #endif + """ + void Py_SET_SIZE(object, Py_ssize_t) + +# Unused bits in every PyLong digit +cdef size_t PyLong_nails = 8*sizeof(digit) - PyLong_SHIFT + cdef inline int fmpz_set_pylong(fmpz_t x, obj): cdef Py_ssize_t length cdef __mpz_struct* mpz_val cdef bint negative - length = Py_SIZE(obj) + length = Py_SIZE(obj) if length == -1: fmpz_set_si(x, -(((obj).ob_digit[0]))) elif length == 0: @@ -15,8 +34,8 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): if length < 0: negative = True length = -length - mpz_import(mpz_val, length, -1, sizeof((obj).ob_digit[0]),0, - sizeof((obj).ob_digit[0])*8 - PyLong_SHIFT, (obj).ob_digit) + mpz_import(mpz_val, length, -1, sizeof(digit),0, + PyLong_nails, (obj).ob_digit) if negative: mpz_neg(mpz_val, mpz_val) _fmpz_demote_val(x) @@ -34,11 +53,18 @@ cdef fmpz_get_intlong(fmpz_t x): """ Convert fmpz_t to a Python int or long. """ - cdef char * s + cdef size_t nbits + cdef size_t pylong_size + cdef __mpz_struct * z if COEFF_IS_MPZ(x[0]): - s = fmpz_get_str(NULL, 16, x) - v = int(str_from_chars(s), 16) - libc.stdlib.free(s) + z = _fmpz_promote_val(x) + nbits = mpz_sizeinbase(z, 2) + pylong_size = (nbits + PyLong_SHIFT -1) // PyLong_SHIFT + v = _PyLong_New(pylong_size) + mpz_export(v.ob_digit, NULL, -1, sizeof(digit), 0, PyLong_nails, z) + _fmpz_demote_val(x) + if fmpz_sgn(x) < 0: + Py_SET_SIZE(v, -pylong_size) return v else: return x[0] From 6cf02354605c479ece1d671cacb45d4c31e37fad Mon Sep 17 00:00:00 2001 From: David Einstein Date: Wed, 23 Aug 2023 23:14:02 -0400 Subject: [PATCH 4/4] Sped up fmpz_set_pylong in the 2**30 to 2**40 range I'm not entirely sure what is going on, but this seems to be the fastest set of methods --- src/flint/fmpz.pyx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/flint/fmpz.pyx b/src/flint/fmpz.pyx index 461b08ae..f8f3f067 100644 --- a/src/flint/fmpz.pyx +++ b/src/flint/fmpz.pyx @@ -26,14 +26,11 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): cdef Py_ssize_t length cdef __mpz_struct* mpz_val cdef bint negative - length = Py_SIZE(obj) - if length == -1: - fmpz_set_si(x, -(((obj).ob_digit[0]))) - elif length == 0: - fmpz_zero(x) - elif length == 1: - fmpz_set_si(x, (obj).ob_digit[0]) - else: + cdef int overflow + cdef slong longval + longval = pylong_as_slong(obj, &overflow) + if overflow: + length = Py_SIZE(obj) mpz_val = _fmpz_promote(x) negative = False if length < 0: @@ -44,6 +41,8 @@ cdef inline int fmpz_set_pylong(fmpz_t x, obj): if negative: mpz_neg(mpz_val, mpz_val) _fmpz_demote_val(x) + else: + fmpz_set_si(x, longval) cdef inline int fmpz_set_python(fmpz_t x, obj): if PY_MAJOR_VERSION < 3 and PyInt_Check(obj):