Optimise numeric division for 3 and 4 base-NBASE digit divisors.
On platforms with 128-bit integer support, introduce a new function div_var_int64(), along the same lines as div_var_int() added in d1b307eef2 for divisors with 1 or 2 base-NBASE digits, and use it to speed up div_var() and div_var_fast() in a similar way when the divisor has 3 or 4 base-NBASE digits. This gives significant performance gains for divisors with 9-16 decimal digits. Joel Jacobson. Discussion: https://postgr.es/m/b7a5893d-af18-4c0b-8918-96de5f1bbf39%40app.fastmail.com https://postgr.es/m/CAEZATCXGm%3DDyTq%3DFrcOqC0gPMVveKUYTaD5KRRoajrUTiWxVMw%40mail.gmail.com
This commit is contained in:
parent
009dbdea02
commit
0aa38db56b
@ -554,6 +554,10 @@ static void div_var_fast(const NumericVar *var1, const NumericVar *var2,
|
|||||||
NumericVar *result, int rscale, bool round);
|
NumericVar *result, int rscale, bool round);
|
||||||
static void div_var_int(const NumericVar *var, int ival, int ival_weight,
|
static void div_var_int(const NumericVar *var, int ival, int ival_weight,
|
||||||
NumericVar *result, int rscale, bool round);
|
NumericVar *result, int rscale, bool round);
|
||||||
|
#ifdef HAVE_INT128
|
||||||
|
static void div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
|
||||||
|
NumericVar *result, int rscale, bool round);
|
||||||
|
#endif
|
||||||
static int select_div_scale(const NumericVar *var1, const NumericVar *var2);
|
static int select_div_scale(const NumericVar *var1, const NumericVar *var2);
|
||||||
static void mod_var(const NumericVar *var1, const NumericVar *var2,
|
static void mod_var(const NumericVar *var1, const NumericVar *var2,
|
||||||
NumericVar *result);
|
NumericVar *result);
|
||||||
@ -8484,6 +8488,9 @@ div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
|
|||||||
/*
|
/*
|
||||||
* If the divisor has just one or two digits, delegate to div_var_int(),
|
* If the divisor has just one or two digits, delegate to div_var_int(),
|
||||||
* which uses fast short division.
|
* which uses fast short division.
|
||||||
|
*
|
||||||
|
* Similarly, on platforms with 128-bit integer support, delegate to
|
||||||
|
* div_var_int64() for divisors with three or four digits.
|
||||||
*/
|
*/
|
||||||
if (var2ndigits <= 2)
|
if (var2ndigits <= 2)
|
||||||
{
|
{
|
||||||
@ -8503,6 +8510,26 @@ div_var(const NumericVar *var1, const NumericVar *var2, NumericVar *result,
|
|||||||
div_var_int(var1, idivisor, idivisor_weight, result, rscale, round);
|
div_var_int(var1, idivisor, idivisor_weight, result, rscale, round);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_INT128
|
||||||
|
if (var2ndigits <= 4)
|
||||||
|
{
|
||||||
|
int64 idivisor;
|
||||||
|
int idivisor_weight;
|
||||||
|
|
||||||
|
idivisor = var2->digits[0];
|
||||||
|
idivisor_weight = var2->weight;
|
||||||
|
for (i = 1; i < var2ndigits; i++)
|
||||||
|
{
|
||||||
|
idivisor = idivisor * NBASE + var2->digits[i];
|
||||||
|
idivisor_weight--;
|
||||||
|
}
|
||||||
|
if (var2->sign == NUMERIC_NEG)
|
||||||
|
idivisor = -idivisor;
|
||||||
|
|
||||||
|
div_var_int64(var1, idivisor, idivisor_weight, result, rscale, round);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Otherwise, perform full long division.
|
* Otherwise, perform full long division.
|
||||||
@ -8774,6 +8801,9 @@ div_var_fast(const NumericVar *var1, const NumericVar *var2,
|
|||||||
/*
|
/*
|
||||||
* If the divisor has just one or two digits, delegate to div_var_int(),
|
* If the divisor has just one or two digits, delegate to div_var_int(),
|
||||||
* which uses fast short division.
|
* which uses fast short division.
|
||||||
|
*
|
||||||
|
* Similarly, on platforms with 128-bit integer support, delegate to
|
||||||
|
* div_var_int64() for divisors with three or four digits.
|
||||||
*/
|
*/
|
||||||
if (var2ndigits <= 2)
|
if (var2ndigits <= 2)
|
||||||
{
|
{
|
||||||
@ -8793,6 +8823,26 @@ div_var_fast(const NumericVar *var1, const NumericVar *var2,
|
|||||||
div_var_int(var1, idivisor, idivisor_weight, result, rscale, round);
|
div_var_int(var1, idivisor, idivisor_weight, result, rscale, round);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef HAVE_INT128
|
||||||
|
if (var2ndigits <= 4)
|
||||||
|
{
|
||||||
|
int64 idivisor;
|
||||||
|
int idivisor_weight;
|
||||||
|
|
||||||
|
idivisor = var2->digits[0];
|
||||||
|
idivisor_weight = var2->weight;
|
||||||
|
for (i = 1; i < var2ndigits; i++)
|
||||||
|
{
|
||||||
|
idivisor = idivisor * NBASE + var2->digits[i];
|
||||||
|
idivisor_weight--;
|
||||||
|
}
|
||||||
|
if (var2->sign == NUMERIC_NEG)
|
||||||
|
idivisor = -idivisor;
|
||||||
|
|
||||||
|
div_var_int64(var1, idivisor, idivisor_weight, result, rscale, round);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Otherwise, perform full long division.
|
* Otherwise, perform full long division.
|
||||||
@ -9182,6 +9232,123 @@ div_var_int(const NumericVar *var, int ival, int ival_weight,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAVE_INT128
|
||||||
|
/*
|
||||||
|
* div_var_int64() -
|
||||||
|
*
|
||||||
|
* Divide a numeric variable by a 64-bit integer with the specified weight.
|
||||||
|
* The quotient var / (ival * NBASE^ival_weight) is stored in result.
|
||||||
|
*
|
||||||
|
* This duplicates the logic in div_var_int(), so any changes made there
|
||||||
|
* should be made here too.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
div_var_int64(const NumericVar *var, int64 ival, int ival_weight,
|
||||||
|
NumericVar *result, int rscale, bool round)
|
||||||
|
{
|
||||||
|
NumericDigit *var_digits = var->digits;
|
||||||
|
int var_ndigits = var->ndigits;
|
||||||
|
int res_sign;
|
||||||
|
int res_weight;
|
||||||
|
int res_ndigits;
|
||||||
|
NumericDigit *res_buf;
|
||||||
|
NumericDigit *res_digits;
|
||||||
|
uint64 divisor;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Guard against division by zero */
|
||||||
|
if (ival == 0)
|
||||||
|
ereport(ERROR,
|
||||||
|
errcode(ERRCODE_DIVISION_BY_ZERO),
|
||||||
|
errmsg("division by zero"));
|
||||||
|
|
||||||
|
/* Result zero check */
|
||||||
|
if (var_ndigits == 0)
|
||||||
|
{
|
||||||
|
zero_var(result);
|
||||||
|
result->dscale = rscale;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine the result sign, weight and number of digits to calculate.
|
||||||
|
* The weight figured here is correct if the emitted quotient has no
|
||||||
|
* leading zero digits; otherwise strip_var() will fix things up.
|
||||||
|
*/
|
||||||
|
if (var->sign == NUMERIC_POS)
|
||||||
|
res_sign = ival > 0 ? NUMERIC_POS : NUMERIC_NEG;
|
||||||
|
else
|
||||||
|
res_sign = ival > 0 ? NUMERIC_NEG : NUMERIC_POS;
|
||||||
|
res_weight = var->weight - ival_weight;
|
||||||
|
/* The number of accurate result digits we need to produce: */
|
||||||
|
res_ndigits = res_weight + 1 + (rscale + DEC_DIGITS - 1) / DEC_DIGITS;
|
||||||
|
/* ... but always at least 1 */
|
||||||
|
res_ndigits = Max(res_ndigits, 1);
|
||||||
|
/* If rounding needed, figure one more digit to ensure correct result */
|
||||||
|
if (round)
|
||||||
|
res_ndigits++;
|
||||||
|
|
||||||
|
res_buf = digitbuf_alloc(res_ndigits + 1);
|
||||||
|
res_buf[0] = 0; /* spare digit for later rounding */
|
||||||
|
res_digits = res_buf + 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now compute the quotient digits. This is the short division algorithm
|
||||||
|
* described in Knuth volume 2, section 4.3.1 exercise 16, except that we
|
||||||
|
* allow the divisor to exceed the internal base.
|
||||||
|
*
|
||||||
|
* In this algorithm, the carry from one digit to the next is at most
|
||||||
|
* divisor - 1. Therefore, while processing the next digit, carry may
|
||||||
|
* become as large as divisor * NBASE - 1, and so it requires a 128-bit
|
||||||
|
* integer if this exceeds PG_UINT64_MAX.
|
||||||
|
*/
|
||||||
|
divisor = i64abs(ival);
|
||||||
|
|
||||||
|
if (divisor <= PG_UINT64_MAX / NBASE)
|
||||||
|
{
|
||||||
|
/* carry cannot overflow 64 bits */
|
||||||
|
uint64 carry = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < res_ndigits; i++)
|
||||||
|
{
|
||||||
|
carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0);
|
||||||
|
res_digits[i] = (NumericDigit) (carry / divisor);
|
||||||
|
carry = carry % divisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* carry may exceed 64 bits */
|
||||||
|
uint128 carry = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < res_ndigits; i++)
|
||||||
|
{
|
||||||
|
carry = carry * NBASE + (i < var_ndigits ? var_digits[i] : 0);
|
||||||
|
res_digits[i] = (NumericDigit) (carry / divisor);
|
||||||
|
carry = carry % divisor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store the quotient in result */
|
||||||
|
digitbuf_free(result->buf);
|
||||||
|
result->ndigits = res_ndigits;
|
||||||
|
result->buf = res_buf;
|
||||||
|
result->digits = res_digits;
|
||||||
|
result->weight = res_weight;
|
||||||
|
result->sign = res_sign;
|
||||||
|
|
||||||
|
/* Round or truncate to target rscale (and set result->dscale) */
|
||||||
|
if (round)
|
||||||
|
round_var(result, rscale);
|
||||||
|
else
|
||||||
|
trunc_var(result, rscale);
|
||||||
|
|
||||||
|
/* Strip leading/trailing zeroes */
|
||||||
|
strip_var(result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default scale selection for division
|
* Default scale selection for division
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user