From f38f0c972f9bfc6d183ec6f949c4bb4ef61c27a8 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Fri, 26 Dec 2025 01:28:05 +1300 Subject: [PATCH v1 2/3] Experimental code for better NULL handing in tuple deform code This introduces next_null_until() which processes the tuple's NULL bitmask to search for the first NULL after a certain point and returns the attnum of that NULL and if consecutive NULLs follow that NULL, then return the attnum of the first non-NULL after the sequence of NULLs. This allows us to deform the tuple in a dedicated loop that never checks the NULL bitmask because we only ever deform until one before a NULL attribute. We break out the dedicated loop into a NULL handling loop then go back into the non-NULL loop to processes remaining non-NULL attributes until the tuple is deformed. --- src/backend/executor/execTuples.c | 112 +++++++++++++++++++----------- src/include/access/tupmacs.h | 91 ++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 41 deletions(-) diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 6d33f494a70..18e7db12dab 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -1022,7 +1022,8 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, #ifdef OPTIMIZE_BYVAL int firstByRefAttr; #endif - int firstNullAttr; + int nextNullAttr; + int nextNullSeqEnd; Datum *values; bool *isnull; char *tp; /* ptr to tuple data */ @@ -1036,13 +1037,20 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, if (hasnulls) { bp = tup->t_bits; - firstNullAttr = first_null_attr(bp, natts); - firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, firstNullAttr); + next_null_until(bp, 0, natts, &nextNullAttr, &nextNullSeqEnd); + firstNonCacheOffsetAttr = Min(firstNonCacheOffsetAttr, nextNullAttr); + + /* + * While we're here, we can unset hasnulls if there's no NULL found. + * Remember that we might not be deforming the entire tuple here, so + * HeapTupleHasNulls() may just be true for some later attribute. + */ + hasnulls = (nextNullAttr < natts); } else { bp = NULL; - firstNullAttr = natts; + nextNullAttr = natts; } #ifdef OPTIMIZE_BYVAL @@ -1121,54 +1129,76 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, off = *offp; } - /* - * Handle any portion of the tuple that doesn't have a fixed offset up - * until the first NULL attribute. This loops only differs from the one - * after it by the NULL checks. - */ - for (; attnum < firstNullAttr; attnum++) + /* Handle the remaining part of the tuple. */ + if (!hasnulls) { - cattr = TupleDescCompactAttr(tupleDesc, attnum); + /* + * If there are no NULLs before natts, then use a simple loop without + * NULL handling. + */ + for (; attnum < natts; attnum++) + { + cattr = TupleDescCompactAttr(tupleDesc, attnum); - /* align the offset for this attribute */ - off = att_pointer_alignby(off, - cattr->attalignby, - cattr->attlen, - tp + off); + /* align the offset for this attribute */ + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); - values[attnum] = fetchatt(cattr, tp + off); - isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); + isnull[attnum] = false; - /* move the offset beyond this attribute */ - off = att_addlength_pointer(off, cattr->attlen, tp + off); + /* move the offset beyond this attribute */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); + } } - - /* - * Now handle any remaining tuples, this time include NULL checks as we're - * now at the first NULL attribute. - */ - for (; attnum < natts; attnum++) + else { - if (att_isnull(attnum, bp)) + /* + * Otherwise, we need to handle NULLs. Rather than going to the + * trouble of calling att_isnull(), we instead do some processing on + * the bit mask to find the next NULL bit and how many follow that + * then process using two loops, the first of the inner loops here + * never sees a NULL attribute as the loop will end before we get to a + * NULL attr, the 2nd loop takes over and processes all the NULLs and + * we'll go back to the first loop and handle any remaining non-NULL + * attributes. + */ + for (;;) { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - continue; - } + for (; attnum < nextNullAttr; attnum++) + { + Assert(!att_isnull(attnum, bp)); - cattr = TupleDescCompactAttr(tupleDesc, attnum); + cattr = TupleDescCompactAttr(tupleDesc, attnum); - /* align the offset for this attribute */ - off = att_pointer_alignby(off, - cattr->attalignby, - cattr->attlen, - tp + off); + /* align the offset for this attribute */ + off = att_pointer_alignby(off, + cattr->attalignby, + cattr->attlen, + tp + off); - values[attnum] = fetchatt(cattr, tp + off); - isnull[attnum] = false; + values[attnum] = fetchatt(cattr, tp + off); + isnull[attnum] = false; - /* move the offset beyond this attribute */ - off = att_addlength_pointer(off, cattr->attlen, tp + off); + /* move the offset beyond this attribute */ + off = att_addlength_pointer(off, cattr->attlen, tp + off); + } + + if (likely(attnum == natts)) + break; + + /* Handle the NULLs */ + for (; unlikely(attnum < nextNullSeqEnd); attnum++) + { + Assert(att_isnull(attnum, bp)); + isnull[attnum] = true; + } + + /* Locate the next NULL, if any */ + next_null_until(bp, attnum, natts, &nextNullAttr, &nextNullSeqEnd); + } } /* diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h index d6ab90bbde1..b2a16fd08b8 100644 --- a/src/include/access/tupmacs.h +++ b/src/include/access/tupmacs.h @@ -71,6 +71,97 @@ fetch_att(const void *T, bool attbyval, int attlen) return PointerGetDatum(T); } +/* + * next_null_until + * Process 'bits' and look for the next bit marked as NULL (a 0 bit) + * starting at startAttr and set the 0-based position of the first NULL + * in *firstNull. The function then continues to determine the index of + * the last consecutive NULL that comes directly after the firstNull. + * When no NULLs are found, *firstNull and *nullsUntil are both set to + * natts. + */ +static inline void +next_null_until(const bits8 *bits, int startAttr, int natts, int *firstNull, int *nullsUntil) +{ + int lastByte = natts >> 3; + int firstByte = startAttr >> 3; + int first = natts; + int until = natts; + bits8 byte; + bits8 mask; + + /* + * Start searching for the first 0 bit starting at startAttr. + */ + + /* Don't consider bits prior to startAttr */ + mask = 0xFF >> (startAttr & 7) << (startAttr & 7); + for (int i = firstByte; i < lastByte; i++) + { + byte = (~bits[i]) & mask; + + /* did we find a NULL? */ + if (byte != 0) + { + first = i * 8 + pg_rightmost_one_pos[byte]; + goto searchUntil; + } + + /* consider all bits for whole intermediate bytes */ + mask = 0xFF; + } + + /* consider the final byte, but only up until the natts'th bit */ + mask &= ((((bits8) 1) << (natts & 7)) - 1); + byte = (~bits[lastByte]) & mask; + + /* + * Record the position of the 0 value bit, or if we didn't find one, then + * we're done. + */ + if (byte != 0) + first = lastByte * 8 + pg_rightmost_one_pos[byte]; + else + goto done; + +searchUntil: + + /* + * Now check how many 0 bits follow the 'first' bit. + */ + + firstByte = (first + 1) >> 3; + + /* don't consider bits before first + 1 */ + mask = 0xFF >> ((first + 1) & 7) << ((first + 1) & 7); + for (int i = firstByte; i < lastByte; i++) + { + byte = bits[i] & mask; + + /* + * If we found a 1-bit (a non-NULL) then record that the 0-bits ended + * one bit prior to that. + */ + if (byte != 0) + { + until = i * 8 + pg_rightmost_one_pos[byte]; + goto done; + } + /* switch to considering all bits for intermediate bytes */ + mask = 0xFF; + } + + /* Update the mask to mask out anything after natts */ + mask &= ((((bits8) 1) << (natts & 7)) - 1); + byte = bits[lastByte] & mask; + if (byte != 0) + until = lastByte * 8 + pg_rightmost_one_pos[byte]; + +done: + *firstNull = first; + *nullsUntil = until; +} + /* * first_null_attr * Inspect a NULL bitmask from a tuple and return the 0-based attnum of the -- 2.43.0