v1-0002-Experimental-code-for-better-NULL-handing-in-tupl.patch

text/plain

Filename: v1-0002-Experimental-code-for-better-NULL-handing-in-tupl.patch
Type: text/plain
Part: 4
Message: More speedups for tuple deformation
From f38f0c972f9bfc6d183ec6f949c4bb4ef61c27a8 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
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