Commit fe042e9d authored by Paul Eggert's avatar Paul Eggert

Speed up (+ 2 2) by a factor of 10

Improve arithmetic performance by avoiding bignums until needed.
Also, simplify bignum memory management, fixing some unlikely leaks.
This patch improved the performance of (+ 2 2) by a factor of ten
on a simple microbenchmark computing (+ x 2), byte-compiled,
with x a local variable initialized to 2 via means the byte
compiler could not predict: performance improved from 135 to 13 ns.
The platform was Fedora 28 x86-64, AMD Phenom II X4 910e.
Performance also improved 0.6% on ‘make compile-always’.
* src/bignum.c (init_bignum_once): New function.
* src/emacs.c (main): Use it.
* src/bignum.c (mpz): New global var.
(make_integer_mpz): Rename from make_integer.  All uses changed.
* src/bignum.c (double_to_bignum, make_bignum_bits)
(make_bignum, make_bigint, make_biguint, make_integer_mpz):
* src/data.c (bignum_arith_driver, Frem, Flogcount, Fash)
(expt_integer, Fadd1, Fsub1, Flognot):
* src/floatfns.c (Fabs, rounding_driver, rounddiv_q):
* src/fns.c (Fnthcdr):
Use mpz rather than mpz_initting and mpz_clearing private
temporaries.
* src/bignum.h (bignum_integer): New function.
* src/data.c (Frem, Fmod, Fash, expt_integer):
* src/floatfns.c (rounding_driver):
Use it to simplify code.
* src/data.c (FIXNUMS_FIT_IN_LONG, free_mpz_value):
Remove.  All uses removed.
(floating_point_op): New function.
(floatop_arith_driver): New function, with much of the guts
of the old float_arith_driver.
(float_arith_driver): Use it.
(floatop_arith_driver, arith_driver):
Simplify by assuming NARGS is at least 2.
All callers changed.
(float_arith_driver):
New arg, containing the partly converted value of the next arg.
Reorder args for consistency.  All uses changed.
(bignum_arith_driver): New function.
(arith_driver): Use it.  Do fixnum-only integer calculations
in intmax_t instead of mpz_t, when they fit.
Break out mpz_t calculations into bignum_arith_driver.
(Fquo): Use floatop_arith_driver instead of float_arith_driver,
since the op is known to be valid.
(Flogcount, Fash): Simplify by coalescing bignum and fixnum code.
(Fadd1, Fsub1): Simplify by using make_int.
parent 40f8ade7
......@@ -25,6 +25,22 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include <stdlib.h>
/* mpz global temporaries. Making them global saves the trouble of
properly using mpz_init and mpz_clear on temporaries even when
storage is exhausted. Admittedly this is not ideal. An mpz value
in a temporary is made permanent by mpz_swapping it with a bignum's
value. Although typically at most two temporaries are needed,
rounding_driver and rounddiv_q need four altogther. */
mpz_t mpz[4];
void
init_bignum_once (void)
{
for (int i = 0; i < ARRAYELTS (mpz); i++)
mpz_init (mpz[i]);
}
/* Return the value of the Lisp bignum N, as a double. */
double
bignum_to_double (Lisp_Object n)
......@@ -36,17 +52,14 @@ bignum_to_double (Lisp_Object n)
Lisp_Object
double_to_bignum (double d)
{
mpz_t z;
mpz_init_set_d (z, d);
Lisp_Object result = make_integer (z);
mpz_clear (z);
return result;
mpz_set_d (mpz[0], d);
return make_integer_mpz ();
}
/* Return a Lisp integer equal to OP, which has BITS bits and which
must not be in fixnum range. */
/* Return a Lisp integer equal to mpz[0], which has BITS bits and which
must not be in fixnum range. Set mpz[0] to a junk value. */
static Lisp_Object
make_bignum_bits (mpz_t const op, size_t bits)
make_bignum_bits (size_t bits)
{
/* The documentation says integer-width should be nonnegative, so
a single comparison suffices even though 'bits' is unsigned. */
......@@ -55,18 +68,17 @@ make_bignum_bits (mpz_t const op, size_t bits)
struct Lisp_Bignum *b = ALLOCATE_PSEUDOVECTOR (struct Lisp_Bignum, value,
PVEC_BIGNUM);
/* We could mpz_init + mpz_swap here, to avoid a copy, but the
resulting API seemed possibly confusing. */
mpz_init_set (b->value, op);
mpz_init (b->value);
mpz_swap (b->value, mpz[0]);
return make_lisp_ptr (b, Lisp_Vectorlike);
}
/* Return a Lisp integer equal to OP, which must not be in fixnum range. */
/* Return a Lisp integer equal to mpz[0], which must not be in fixnum range.
Set mpz[0] to a junk value. */
static Lisp_Object
make_bignum (mpz_t const op)
make_bignum (void)
{
return make_bignum_bits (op, mpz_sizeinbase (op, 2));
return make_bignum_bits (mpz_sizeinbase (mpz[0], 2));
}
static void mpz_set_uintmax_slow (mpz_t, uintmax_t);
......@@ -86,30 +98,23 @@ Lisp_Object
make_bigint (intmax_t n)
{
eassert (FIXNUM_OVERFLOW_P (n));
mpz_t z;
mpz_init (z);
mpz_set_intmax (z, n);
Lisp_Object result = make_bignum (z);
mpz_clear (z);
return result;
mpz_set_intmax (mpz[0], n);
return make_bignum ();
}
Lisp_Object
make_biguint (uintmax_t n)
{
eassert (FIXNUM_OVERFLOW_P (n));
mpz_t z;
mpz_init (z);
mpz_set_uintmax (z, n);
Lisp_Object result = make_bignum (z);
mpz_clear (z);
return result;
mpz_set_uintmax (mpz[0], n);
return make_bignum ();
}
/* Return a Lisp integer with value taken from OP. */
/* Return a Lisp integer with value taken from mpz[0].
Set mpz[0] to a junk value. */
Lisp_Object
make_integer (mpz_t const op)
make_integer_mpz (void)
{
size_t bits = mpz_sizeinbase (op, 2);
size_t bits = mpz_sizeinbase (mpz[0], 2);
if (bits <= FIXNUM_BITS)
{
......@@ -118,20 +123,20 @@ make_integer (mpz_t const op)
do
{
EMACS_INT limb = mpz_getlimbn (op, i++);
EMACS_INT limb = mpz_getlimbn (mpz[0], i++);
v += limb << shift;
shift += GMP_NUMB_BITS;
}
while (shift < bits);
if (mpz_sgn (op) < 0)
if (mpz_sgn (mpz[0]) < 0)
v = -v;
if (!FIXNUM_OVERFLOW_P (v))
return make_fixnum (v);
}
return make_bignum_bits (op, bits);
return make_bignum_bits (bits);
}
/* Set RESULT to V. This code is for when intmax_t is wider than long. */
......
......@@ -41,7 +41,10 @@ struct Lisp_Bignum
mpz_t value;
};
extern Lisp_Object make_integer (mpz_t const) ARG_NONNULL ((1));
extern mpz_t mpz[4];
extern void init_bignum_once (void);
extern Lisp_Object make_integer_mpz (void);
extern void mpz_set_intmax_slow (mpz_t, intmax_t) ARG_NONNULL ((1));
INLINE_HEADER_BEGIN
......@@ -65,6 +68,20 @@ mpz_set_intmax (mpz_t result, intmax_t v)
mpz_set_intmax_slow (result, v);
}
/* Return a pointer to an mpz_t that is equal to the Lisp integer I.
If I is a bignum this returns a pointer to I's representation;
otherwise this sets *TMP to I's value and returns TMP. */
INLINE mpz_t *
bignum_integer (mpz_t *tmp, Lisp_Object i)
{
if (FIXNUMP (i))
{
mpz_set_intmax (*tmp, XFIXNUM (i));
return tmp;
}
return &XBIGNUM (i)->value;
}
INLINE_HEADER_END
#endif /* BIGNUM_H */
This diff is collapsed.
......@@ -1209,6 +1209,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
if (!initialized)
{
init_alloc_once ();
init_bignum_once ();
init_threads_once ();
init_obarray ();
init_eval_once ();
......
......@@ -270,11 +270,8 @@ DEFUN ("abs", Fabs, Sabs, 1, 1, 0,
{
if (mpz_sgn (XBIGNUM (arg)->value) < 0)
{
mpz_t val;
mpz_init (val);
mpz_neg (val, XBIGNUM (arg)->value);
arg = make_integer (val);
mpz_clear (val);
mpz_neg (mpz[0], XBIGNUM (arg)->value);
arg = make_integer_mpz ();
}
}
......@@ -360,20 +357,10 @@ rounding_driver (Lisp_Object arg, Lisp_Object divisor,
{
if (EQ (divisor, make_fixnum (0)))
xsignal0 (Qarith_error);
mpz_t d, q;
mpz_init (d);
mpz_init (q);
int_divide (q,
(FIXNUMP (arg)
? (mpz_set_intmax (q, XFIXNUM (arg)), q)
: XBIGNUM (arg)->value),
(FIXNUMP (divisor)
? (mpz_set_intmax (d, XFIXNUM (divisor)), d)
: XBIGNUM (divisor)->value));
Lisp_Object result = make_integer (q);
mpz_clear (d);
mpz_clear (q);
return result;
int_divide (mpz[0],
*bignum_integer (&mpz[0], arg),
*bignum_integer (&mpz[1], divisor));
return make_integer_mpz ();
}
double f1 = FLOATP (arg) ? XFLOAT_DATA (arg) : XFIXNUM (arg);
......@@ -417,20 +404,15 @@ rounddiv_q (mpz_t q, mpz_t const n, mpz_t const d)
if (abs_r1 < abs_r + (q & 1))
q += neg_d == neg_r ? 1 : -1; */
mpz_t r, abs_r1;
mpz_init (r);
mpz_init (abs_r1);
mpz_tdiv_qr (q, r, n, d);
mpz_t *r = &mpz[2], *abs_r = r, *abs_r1 = &mpz[3];
mpz_tdiv_qr (q, *r, n, d);
bool neg_d = mpz_sgn (d) < 0;
bool neg_r = mpz_sgn (r) < 0;
mpz_t *abs_r = &r;
mpz_abs (*abs_r, r);
mpz_abs (abs_r1, d);
mpz_sub (abs_r1, abs_r1, *abs_r);
if (mpz_cmp (abs_r1, *abs_r) < (mpz_odd_p (q) != 0))
bool neg_r = mpz_sgn (*r) < 0;
mpz_abs (*abs_r, *r);
mpz_abs (*abs_r1, d);
mpz_sub (*abs_r1, *abs_r1, *abs_r);
if (mpz_cmp (*abs_r1, *abs_r) < (mpz_odd_p (q) != 0))
(neg_d == neg_r ? mpz_add_ui : mpz_sub_ui) (q, q, 1);
mpz_clear (r);
mpz_clear (abs_r1);
}
/* The code uses emacs_rint, so that it works to undefine HAVE_RINT
......
......@@ -1468,19 +1468,17 @@ DEFUN ("nthcdr", Fnthcdr, Snthcdr, 2, 2, 0,
/* Undo any error introduced when LARGE_NUM was substituted for
N, by adding N - LARGE_NUM to NUM, using arithmetic modulo
CYCLE_LENGTH. */
mpz_t z; /* N mod CYCLE_LENGTH. */
mpz_init (z);
/* Add N mod CYCLE_LENGTH to NUM. */
if (cycle_length <= ULONG_MAX)
num += mpz_mod_ui (z, XBIGNUM (n)->value, cycle_length);
num += mpz_mod_ui (mpz[0], XBIGNUM (n)->value, cycle_length);
else
{
mpz_set_intmax (z, cycle_length);
mpz_mod (z, XBIGNUM (n)->value, z);
mpz_set_intmax (mpz[0], cycle_length);
mpz_mod (mpz[0], XBIGNUM (n)->value, mpz[0]);
intptr_t iz;
mpz_export (&iz, NULL, -1, sizeof iz, 0, 0, z);
mpz_export (&iz, NULL, -1, sizeof iz, 0, 0, mpz[0]);
num += iz;
}
mpz_clear (z);
num += cycle_length - large_num % cycle_length;
}
num %= cycle_length;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment