Skip to content

Commit ac0b8b7

Browse files
sohardforanamehackorum
authored andcommitted
numeric_opt
1 parent 19733fa commit ac0b8b7

1 file changed

Lines changed: 246 additions & 28 deletions

File tree

src/backend/utils/adt/numeric.c

Lines changed: 246 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ typedef int16 NumericDigit;
104104
#endif
105105

106106
#define NBASE_SQR (NBASE * NBASE)
107+
#define NUMERIC_STACK_BUFFER_SIZE 64
107108

108109
/*
109110
* The Numeric type as stored on disk.
@@ -494,6 +495,19 @@ static void dump_var(const char *str, NumericVar *var);
494495
(weight) <= NUMERIC_SHORT_WEIGHT_MAX && \
495496
(weight) >= NUMERIC_SHORT_WEIGHT_MIN)
496497

498+
#define COPY_NUMERIC(src, data, digits, ndigit, buffer) \
499+
do { \
500+
if (VARATT_IS_1B((src))) \
501+
{ \
502+
memcpy((buffer), (data), (ndigit) * sizeof(NumericDigit)); \
503+
(digits) = (buffer); \
504+
} \
505+
else \
506+
{ \
507+
(digits) = (NumericDigit *) (data); \
508+
} \
509+
} while (0)
510+
497511
static void alloc_var(NumericVar *var, int ndigits);
498512
static void free_var(NumericVar *var);
499513
static void zero_var(NumericVar *var);
@@ -2525,6 +2539,162 @@ numeric_le(PG_FUNCTION_ARGS)
25252539
PG_RETURN_BOOL(result);
25262540
}
25272541

2542+
/*
2543+
* cmp_numerics_packed() -
2544+
*
2545+
* Compare two packed Numeric varlenas without detoasting short-header
2546+
* datums. This avoids palloc/memcpy overhead for the common case of
2547+
* 1-byte varlena headers (small numerics on heap pages).
2548+
*
2549+
* The key insight: VARDATA_ANY() returns a pointer to the start of the
2550+
* NumericChoice data regardless of whether the varlena has a 1-byte or
2551+
* 4-byte header. We read n_header from there and extract all needed
2552+
* fields using pointer arithmetic rather than the standard NUMERIC_*
2553+
* macros (which assume a 4-byte varlena header via the Numeric struct).
2554+
*/
2555+
static int
2556+
cmp_numerics_packed(Numeric num1, Numeric num2)
2557+
{
2558+
uint16 header1;
2559+
uint16 header2;
2560+
char *data1;
2561+
char *data2;
2562+
NumericDigit digit1_buffer[64];
2563+
NumericDigit digit2_buffer[64];
2564+
int result;
2565+
2566+
/*
2567+
* Get pointers to the NumericChoice data, which starts right after the
2568+
* varlena header (1 or 4 bytes).
2569+
*/
2570+
data1 = VARDATA_ANY(num1);
2571+
data2 = VARDATA_ANY(num2);
2572+
2573+
/*
2574+
* Read the n_header words. We must use memcpy because data1/data2 may
2575+
* be unaligned (when the varlena has a 1-byte header, the data starts at
2576+
* an odd offset).
2577+
*/
2578+
memcpy(&header1, data1, sizeof(uint16));
2579+
memcpy(&header2, data2, sizeof(uint16));
2580+
2581+
/* Handle special values (NaN, Inf) — same logic as cmp_numerics */
2582+
if ((header1 & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL)
2583+
{
2584+
if (header1 == NUMERIC_NAN)
2585+
{
2586+
if (header2 == NUMERIC_NAN)
2587+
result = 0; /* NAN = NAN */
2588+
else
2589+
result = 1; /* NAN > non-NAN */
2590+
}
2591+
else if (header1 == NUMERIC_PINF)
2592+
{
2593+
if (header2 == NUMERIC_NAN)
2594+
result = -1; /* PINF < NAN */
2595+
else if (header2 == NUMERIC_PINF)
2596+
result = 0; /* PINF = PINF */
2597+
else
2598+
result = 1; /* PINF > anything else */
2599+
}
2600+
else /* num1 must be NINF */
2601+
{
2602+
if (header2 == NUMERIC_NINF)
2603+
result = 0; /* NINF = NINF */
2604+
else
2605+
result = -1; /* NINF < anything else */
2606+
}
2607+
}
2608+
else if ((header2 & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL)
2609+
{
2610+
if (header2 == NUMERIC_NINF)
2611+
result = 1; /* normal > NINF */
2612+
else
2613+
result = -1; /* normal < NAN or PINF */
2614+
}
2615+
else
2616+
{
2617+
/*
2618+
* Both are regular numerics. Extract fields from the raw data.
2619+
*
2620+
* For short-format numerics (header & 0x8000 != 0):
2621+
* - n_header is 2 bytes, digits follow immediately
2622+
* - sign is encoded in bit 0x2000
2623+
* - weight is in low 7 bits with sign extension
2624+
*
2625+
* For long-format numerics (header & 0x8000 == 0):
2626+
* - n_sign_dscale is 2 bytes, then n_weight is 2 bytes, then digits
2627+
* - sign is in high 2 bits of n_sign_dscale
2628+
*/
2629+
int sign1,
2630+
sign2;
2631+
int weight1,
2632+
weight2;
2633+
NumericDigit *digits1,
2634+
*digits2;
2635+
int ndigits1,
2636+
ndigits2;
2637+
int data_len1,
2638+
data_len2;
2639+
2640+
data_len1 = (int) VARSIZE_ANY_EXHDR(num1);
2641+
data_len2 = (int) VARSIZE_ANY_EXHDR(num2);
2642+
2643+
if (header1 & 0x8000)
2644+
{
2645+
/* Short format */
2646+
sign1 = (header1 & NUMERIC_SHORT_SIGN_MASK) ? NUMERIC_NEG : NUMERIC_POS;
2647+
weight1 = (header1 & NUMERIC_SHORT_WEIGHT_SIGN_MASK ?
2648+
~NUMERIC_SHORT_WEIGHT_MASK : 0) |
2649+
(header1 & NUMERIC_SHORT_WEIGHT_MASK);
2650+
ndigits1 = (data_len1 - (int) sizeof(uint16)) / (int) sizeof(NumericDigit);
2651+
2652+
COPY_NUMERIC(num1, data1 + sizeof(uint16), digits1, ndigits1, digit1_buffer);
2653+
}
2654+
else
2655+
{
2656+
/* Long format */
2657+
int16 n_weight1;
2658+
2659+
sign1 = header1 & NUMERIC_SIGN_MASK;
2660+
memcpy(&n_weight1, data1 + sizeof(uint16), sizeof(int16));
2661+
weight1 = n_weight1;
2662+
ndigits1 = (data_len1 - (int) sizeof(uint16) - (int) sizeof(int16)) / (int) sizeof(NumericDigit);
2663+
2664+
COPY_NUMERIC(num1, data1 + sizeof(uint16) + sizeof(int16), digits1, ndigits1, digit1_buffer);
2665+
}
2666+
2667+
if (header2 & 0x8000)
2668+
{
2669+
/* Short format */
2670+
sign2 = (header2 & NUMERIC_SHORT_SIGN_MASK) ? NUMERIC_NEG : NUMERIC_POS;
2671+
weight2 = (header2 & NUMERIC_SHORT_WEIGHT_SIGN_MASK ?
2672+
~NUMERIC_SHORT_WEIGHT_MASK : 0) |
2673+
(header2 & NUMERIC_SHORT_WEIGHT_MASK);
2674+
ndigits2 = (data_len2 - (int) sizeof(uint16)) / (int) sizeof(NumericDigit);
2675+
2676+
COPY_NUMERIC(num2, data2 + sizeof(uint16), digits2, ndigits2, digit2_buffer);
2677+
}
2678+
else
2679+
{
2680+
/* Long format */
2681+
int16 n_weight2;
2682+
2683+
sign2 = header2 & NUMERIC_SIGN_MASK;
2684+
memcpy(&n_weight2, data2 + sizeof(uint16), sizeof(int16));
2685+
weight2 = n_weight2;
2686+
ndigits2 = (data_len2 - (int) sizeof(uint16) - (int) sizeof(int16)) / (int) sizeof(NumericDigit);
2687+
2688+
COPY_NUMERIC(num2, data2 + sizeof(uint16) + sizeof(int16), digits2, ndigits2, digit2_buffer);
2689+
}
2690+
2691+
result = cmp_var_common(digits1, ndigits1, weight1, sign1,
2692+
digits2, ndigits2, weight2, sign2);
2693+
}
2694+
2695+
return result;
2696+
}
2697+
25282698
static int
25292699
cmp_numerics(Numeric num1, Numeric num2)
25302700
{
@@ -3456,17 +3626,18 @@ numeric_inc(PG_FUNCTION_ARGS)
34563626
Datum
34573627
numeric_smaller(PG_FUNCTION_ARGS)
34583628
{
3459-
Numeric num1 = PG_GETARG_NUMERIC(0);
3460-
Numeric num2 = PG_GETARG_NUMERIC(1);
3629+
Numeric num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
3630+
Numeric num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
34613631

34623632
/*
3463-
* Use cmp_numerics so that this will agree with the comparison operators,
3464-
* particularly as regards comparisons involving NaN.
3633+
* Use cmp_numerics_packed so that this will agree with the comparison
3634+
* operators, particularly as regards comparisons involving NaN.
3635+
* This avoids palloc/memcpy overhead for 1-byte varlena headers.
34653636
*/
3466-
if (cmp_numerics(num1, num2) < 0)
3467-
PG_RETURN_NUMERIC(num1);
3637+
if (cmp_numerics_packed(num1, num2) < 0)
3638+
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
34683639
else
3469-
PG_RETURN_NUMERIC(num2);
3640+
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
34703641
}
34713642

34723643

@@ -3478,17 +3649,18 @@ numeric_smaller(PG_FUNCTION_ARGS)
34783649
Datum
34793650
numeric_larger(PG_FUNCTION_ARGS)
34803651
{
3481-
Numeric num1 = PG_GETARG_NUMERIC(0);
3482-
Numeric num2 = PG_GETARG_NUMERIC(1);
3652+
Numeric num1 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0));
3653+
Numeric num2 = (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1));
34833654

34843655
/*
3485-
* Use cmp_numerics so that this will agree with the comparison operators,
3486-
* particularly as regards comparisons involving NaN.
3656+
* Use cmp_numerics_packed so that this will agree with the comparison
3657+
* operators, particularly as regards comparisons involving NaN.
3658+
* This avoids palloc/memcpy overhead for 1-byte varlena headers.
34873659
*/
3488-
if (cmp_numerics(num1, num2) > 0)
3489-
PG_RETURN_NUMERIC(num1);
3660+
if (cmp_numerics_packed(num1, num2) > 0)
3661+
PG_RETURN_DATUM(PG_GETARG_DATUM(0));
34903662
else
3491-
PG_RETURN_NUMERIC(num2);
3663+
PG_RETURN_DATUM(PG_GETARG_DATUM(1));
34923664
}
34933665

34943666

@@ -4814,6 +4986,50 @@ makeNumericAggStateCurrentContext(bool calcSumX2)
48144986
return state;
48154987
}
48164988

4989+
/*
4990+
* Safely initialize a NumericVar from a potentially packed short-header datum.
4991+
*/
4992+
static void
4993+
init_var_from_packed(Numeric num, NumericVar *dest, NumericDigit* digit_buffer)
4994+
{
4995+
uint16 header;
4996+
char *data;
4997+
4998+
data = VARDATA_ANY(num);
4999+
memcpy(&header, data, sizeof(uint16));
5000+
5001+
dest->buf = NULL;
5002+
if ((header & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL)
5003+
{
5004+
dest->ndigits = 0;
5005+
dest->weight = 0;
5006+
dest->sign = header & NUMERIC_EXT_SIGN_MASK;
5007+
dest->dscale = 0;
5008+
dest->digits = NULL;
5009+
}
5010+
else if ((header & 0x8000) != 0)
5011+
{
5012+
dest->ndigits = (VARSIZE_ANY_EXHDR(num) - sizeof(uint16)) / sizeof(NumericDigit);
5013+
dest->weight = (header & NUMERIC_SHORT_WEIGHT_SIGN_MASK ? ~NUMERIC_SHORT_WEIGHT_MASK : 0)
5014+
| (header & NUMERIC_SHORT_WEIGHT_MASK);
5015+
dest->sign = (header & NUMERIC_SHORT_SIGN_MASK) ? NUMERIC_NEG : NUMERIC_POS;
5016+
dest->dscale = (header & NUMERIC_SHORT_DSCALE_MASK) >> NUMERIC_SHORT_DSCALE_SHIFT;
5017+
5018+
COPY_NUMERIC(num, data + sizeof(uint16), dest->digits, dest->ndigits, digit_buffer);
5019+
}
5020+
else
5021+
{
5022+
int16 weight;
5023+
memcpy(&weight, data + sizeof(uint16), sizeof(int16));
5024+
dest->ndigits = (VARSIZE_ANY_EXHDR(num) - sizeof(uint16) - sizeof(int16)) / sizeof(NumericDigit);
5025+
dest->weight = weight;
5026+
dest->sign = header & NUMERIC_SIGN_MASK;
5027+
dest->dscale = header & NUMERIC_DSCALE_MASK;
5028+
5029+
COPY_NUMERIC(num, data + sizeof(uint16) + sizeof(int16), dest->digits, dest->ndigits, digit_buffer);
5030+
}
5031+
}
5032+
48175033
/*
48185034
* Accumulate a new input value for numeric aggregate functions.
48195035
*/
@@ -4822,23 +5038,24 @@ do_numeric_accum(NumericAggState *state, Numeric newval)
48225038
{
48235039
NumericVar X;
48245040
NumericVar X2;
5041+
NumericDigit digit_buffer[NUMERIC_STACK_BUFFER_SIZE];
48255042
MemoryContext old_context;
48265043

5044+
/* load processed number in short-lived context */
5045+
init_var_from_packed(newval, &X, digit_buffer);
5046+
48275047
/* Count NaN/infinity inputs separately from all else */
4828-
if (NUMERIC_IS_SPECIAL(newval))
5048+
if (X.sign == NUMERIC_NAN || X.sign == NUMERIC_PINF || X.sign == NUMERIC_NINF)
48295049
{
4830-
if (NUMERIC_IS_PINF(newval))
5050+
if (X.sign == NUMERIC_PINF)
48315051
state->pInfcount++;
4832-
else if (NUMERIC_IS_NINF(newval))
5052+
else if (X.sign == NUMERIC_NINF)
48335053
state->nInfcount++;
48345054
else
48355055
state->NaNcount++;
48365056
return;
48375057
}
48385058

4839-
/* load processed number in short-lived context */
4840-
init_var_from_num(newval, &X);
4841-
48425059
/*
48435060
* Track the highest input dscale that we've seen, to support inverse
48445061
* transitions (see do_numeric_discard).
@@ -4892,23 +5109,24 @@ do_numeric_discard(NumericAggState *state, Numeric newval)
48925109
{
48935110
NumericVar X;
48945111
NumericVar X2;
5112+
NumericDigit digit_buffer[NUMERIC_STACK_BUFFER_SIZE];
48955113
MemoryContext old_context;
48965114

5115+
/* load processed number in short-lived context */
5116+
init_var_from_packed(newval, &X, digit_buffer);
5117+
48975118
/* Count NaN/infinity inputs separately from all else */
4898-
if (NUMERIC_IS_SPECIAL(newval))
5119+
if (X.sign == NUMERIC_NAN || X.sign == NUMERIC_PINF || X.sign == NUMERIC_NINF)
48995120
{
4900-
if (NUMERIC_IS_PINF(newval))
5121+
if (X.sign == NUMERIC_PINF)
49015122
state->pInfcount--;
4902-
else if (NUMERIC_IS_NINF(newval))
5123+
else if (X.sign == NUMERIC_NINF)
49035124
state->nInfcount--;
49045125
else
49055126
state->NaNcount--;
49065127
return true;
49075128
}
49085129

4909-
/* load processed number in short-lived context */
4910-
init_var_from_num(newval, &X);
4911-
49125130
/*
49135131
* state->sumX's dscale is the maximum dscale of any of the inputs.
49145132
* Removing the last input with that dscale would require us to recompute
@@ -4992,7 +5210,7 @@ numeric_accum(PG_FUNCTION_ARGS)
49925210
state = makeNumericAggState(fcinfo, true);
49935211

49945212
if (!PG_ARGISNULL(1))
4995-
do_numeric_accum(state, PG_GETARG_NUMERIC(1));
5213+
do_numeric_accum(state, (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1)));
49965214

49975215
PG_RETURN_POINTER(state);
49985216
}
@@ -5084,7 +5302,7 @@ numeric_avg_accum(PG_FUNCTION_ARGS)
50845302
state = makeNumericAggState(fcinfo, false);
50855303

50865304
if (!PG_ARGISNULL(1))
5087-
do_numeric_accum(state, PG_GETARG_NUMERIC(1));
5305+
do_numeric_accum(state, (Numeric) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(1)));
50885306

50895307
PG_RETURN_POINTER(state);
50905308
}

0 commit comments

Comments
 (0)