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