@@ -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+
497511static void alloc_var (NumericVar * var , int ndigits );
498512static void free_var (NumericVar * var );
499513static 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+
25282698static int
25292699cmp_numerics (Numeric num1 , Numeric num2 )
25302700{
@@ -3456,17 +3626,18 @@ numeric_inc(PG_FUNCTION_ARGS)
34563626Datum
34573627numeric_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)
34783649Datum
34793650numeric_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