Introduce CompactAttribute array in TupleDesc
authorDavid Rowley <[email protected]>
Tue, 3 Dec 2024 03:50:59 +0000 (16:50 +1300)
committerDavid Rowley <[email protected]>
Tue, 3 Dec 2024 03:50:59 +0000 (16:50 +1300)
The new compact_attrs array stores a few select fields from
FormData_pg_attribute in a more compact way, using only 16 bytes per
column instead of the 104 bytes that FormData_pg_attribute uses.  Using
CompactAttribute allows performance-critical operations such as tuple
deformation to be performed without looking at the FormData_pg_attribute
element in TupleDesc which means fewer cacheline accesses.  With this
change, NAMEDATALEN could be increased with a much smaller negative impact
on performance.

For some workloads, tuple deformation can be the most CPU intensive part
of processing the query.  Some testing with 16 columns on a table
where the first column is variable length showed around a 10% increase in
transactions per second for an OLAP type query performing aggregation on
the 16th column.  However, in certain cases, the increases were much
higher, up to ~25% on one AMD Zen4 machine.

This also makes pg_attribute.attcacheoff redundant.  A follow-on commit
will remove it, thus shrinking the FormData_pg_attribute struct by 4
bytes.

Author: David Rowley
Discussion: https://postgr.es/m/CAApHDvrBztXP3yx=NKNmo3xwFAFhEdyPnvrDg3=M0RhDs+4vYw@mail.gmail.com
Reviewed-by: Andres Freund, Victor Yegorov
14 files changed:
src/backend/access/common/heaptuple.c
src/backend/access/common/indextuple.c
src/backend/access/common/tupdesc.c
src/backend/access/spgist/spgutils.c
src/backend/catalog/index.c
src/backend/commands/tablecmds.c
src/backend/executor/execTuples.c
src/backend/utils/cache/relcache.c
src/backend/utils/cache/typcache.c
src/include/access/htup_details.h
src/include/access/itup.h
src/include/access/tupdesc.h
src/include/access/tupmacs.h
src/tools/pgindent/typedefs.list

index 9e3407bf987aa2a0c9c40172473c04a7c8d085e8..982e7222c49bafd0cfcda79e99173eca242135a1 100644 (file)
 #define VARLENA_ATT_IS_PACKABLE(att) \
    ((att)->attstorage != TYPSTORAGE_PLAIN)
 
+/* FormData_pg_attribute.attstorage != TYPSTORAGE_PLAIN and an attlen of -1 */
+#define COMPACT_ATTR_IS_PACKABLE(att) \
+   ((att)->attlen == -1 && (att)->attispackable)
+
 /*
  * Setup for caching pass-by-ref missing attributes in a way that survives
  * tupleDesc destruction.
@@ -147,12 +151,12 @@ Datum
 getmissingattr(TupleDesc tupleDesc,
               int attnum, bool *isnull)
 {
-   Form_pg_attribute att;
+   CompactAttribute *att;
 
    Assert(attnum <= tupleDesc->natts);
    Assert(attnum > 0);
 
-   att = TupleDescAttr(tupleDesc, attnum - 1);
+   att = TupleDescCompactAttr(tupleDesc, attnum - 1);
 
    if (att->atthasmissing)
    {
@@ -223,15 +227,15 @@ heap_compute_data_size(TupleDesc tupleDesc,
    for (i = 0; i < numberOfAttributes; i++)
    {
        Datum       val;
-       Form_pg_attribute atti;
+       CompactAttribute *atti;
 
        if (isnull[i])
            continue;
 
        val = values[i];
-       atti = TupleDescAttr(tupleDesc, i);
+       atti = TupleDescCompactAttr(tupleDesc, i);
 
-       if (ATT_IS_PACKABLE(atti) &&
+       if (COMPACT_ATTR_IS_PACKABLE(atti) &&
            VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
        {
            /*
@@ -268,7 +272,7 @@ heap_compute_data_size(TupleDesc tupleDesc,
  * Fill in either a data value or a bit in the null bitmask
  */
 static inline void
-fill_val(Form_pg_attribute att,
+fill_val(CompactAttribute *att,
         bits8 **bit,
         int *bitmask,
         char **dataP,
@@ -349,8 +353,7 @@ fill_val(Form_pg_attribute att,
            data_length = VARSIZE_SHORT(val);
            memcpy(data, val, data_length);
        }
-       else if (VARLENA_ATT_IS_PACKABLE(att) &&
-                VARATT_CAN_MAKE_SHORT(val))
+       else if (att->attispackable && VARATT_CAN_MAKE_SHORT(val))
        {
            /* convert to short varlena -- no alignment */
            data_length = VARATT_CONVERTED_SHORT_SIZE(val);
@@ -427,7 +430,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 
    for (i = 0; i < numberOfAttributes; i++)
    {
-       Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
+       CompactAttribute *attr = TupleDescCompactAttr(tupleDesc, i);
 
        fill_val(attr,
                 bitP ? &bitP : NULL,
@@ -461,7 +464,8 @@ heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc)
    Assert(!tupleDesc || attnum <= tupleDesc->natts);
    if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
    {
-       if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
+       if (tupleDesc &&
+           TupleDescCompactAttr(tupleDesc, attnum - 1)->atthasmissing)
            return false;
        else
            return true;
@@ -570,13 +574,13 @@ nocachegetattr(HeapTuple tup,
 
    if (!slow)
    {
-       Form_pg_attribute att;
+       CompactAttribute *att;
 
        /*
         * If we get here, there are no nulls up to and including the target
         * attribute.  If we have a cached offset, we can use it.
         */
-       att = TupleDescAttr(tupleDesc, attnum);
+       att = TupleDescCompactAttr(tupleDesc, attnum);
        if (att->attcacheoff >= 0)
            return fetchatt(att, tp + att->attcacheoff);
 
@@ -591,7 +595,7 @@ nocachegetattr(HeapTuple tup,
 
            for (j = 0; j <= attnum; j++)
            {
-               if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+               if (TupleDescCompactAttr(tupleDesc, j)->attlen <= 0)
                {
                    slow = true;
                    break;
@@ -614,18 +618,18 @@ nocachegetattr(HeapTuple tup,
         * fixed-width columns, in hope of avoiding future visits to this
         * routine.
         */
-       TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+       TupleDescCompactAttr(tupleDesc, 0)->attcacheoff = 0;
 
        /* we might have set some offsets in the slow path previously */
-       while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
+       while (j < natts && TupleDescCompactAttr(tupleDesc, j)->attcacheoff > 0)
            j++;
 
-       off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-           TupleDescAttr(tupleDesc, j - 1)->attlen;
+       off = TupleDescCompactAttr(tupleDesc, j - 1)->attcacheoff +
+           TupleDescCompactAttr(tupleDesc, j - 1)->attlen;
 
        for (; j < natts; j++)
        {
-           Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+           CompactAttribute *att = TupleDescCompactAttr(tupleDesc, j);
 
            if (att->attlen <= 0)
                break;
@@ -639,7 +643,7 @@ nocachegetattr(HeapTuple tup,
 
        Assert(j > attnum);
 
-       off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+       off = TupleDescCompactAttr(tupleDesc, attnum)->attcacheoff;
    }
    else
    {
@@ -659,7 +663,7 @@ nocachegetattr(HeapTuple tup,
        off = 0;
        for (i = 0;; i++)       /* loop exit is at "break" */
        {
-           Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+           CompactAttribute *att = TupleDescCompactAttr(tupleDesc, i);
 
            if (HeapTupleHasNulls(tup) && att_isnull(i, bp))
            {
@@ -707,7 +711,7 @@ nocachegetattr(HeapTuple tup,
        }
    }
 
-   return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+   return fetchatt(TupleDescCompactAttr(tupleDesc, attnum), tp + off);
 }
 
 /* ----------------
@@ -892,7 +896,7 @@ expand_tuple(HeapTuple *targetHeapTuple,
        {
            if (attrmiss[attnum].am_present)
            {
-               Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
+               CompactAttribute *att = TupleDescCompactAttr(tupleDesc, attnum);
 
                targetDataLen = att_align_datum(targetDataLen,
                                                att->attalign,
@@ -1020,8 +1024,7 @@ expand_tuple(HeapTuple *targetHeapTuple,
    /* Now fill in the missing values */
    for (attnum = sourceNatts; attnum < natts; attnum++)
    {
-
-       Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
+       CompactAttribute *attr = TupleDescCompactAttr(tupleDesc, attnum);
 
        if (attrmiss && attrmiss[attnum].am_present)
        {
@@ -1370,7 +1373,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
    for (attnum = 0; attnum < natts; attnum++)
    {
-       Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+       CompactAttribute *thisatt = TupleDescCompactAttr(tupleDesc, attnum);
 
        if (hasnulls && att_isnull(attnum, bp))
        {
index bb2c6a2bcc15da4de3925ca2be1db46732a0afe0..37133ed7f80f3486fe98d4b1a51bd29ea0a87e0b 100644 (file)
@@ -303,13 +303,13 @@ nocache_index_getattr(IndexTuple tup,
 
    if (!slow)
    {
-       Form_pg_attribute att;
+       CompactAttribute *att;
 
        /*
         * If we get here, there are no nulls up to and including the target
         * attribute.  If we have a cached offset, we can use it.
         */
-       att = TupleDescAttr(tupleDesc, attnum);
+       att = TupleDescCompactAttr(tupleDesc, attnum);
        if (att->attcacheoff >= 0)
            return fetchatt(att, tp + att->attcacheoff);
 
@@ -324,7 +324,7 @@ nocache_index_getattr(IndexTuple tup,
 
            for (j = 0; j <= attnum; j++)
            {
-               if (TupleDescAttr(tupleDesc, j)->attlen <= 0)
+               if (TupleDescCompactAttr(tupleDesc, j)->attlen <= 0)
                {
                    slow = true;
                    break;
@@ -347,18 +347,18 @@ nocache_index_getattr(IndexTuple tup,
         * fixed-width columns, in hope of avoiding future visits to this
         * routine.
         */
-       TupleDescAttr(tupleDesc, 0)->attcacheoff = 0;
+       TupleDescCompactAttr(tupleDesc, 0)->attcacheoff = 0;
 
        /* we might have set some offsets in the slow path previously */
-       while (j < natts && TupleDescAttr(tupleDesc, j)->attcacheoff > 0)
+       while (j < natts && TupleDescCompactAttr(tupleDesc, j)->attcacheoff > 0)
            j++;
 
-       off = TupleDescAttr(tupleDesc, j - 1)->attcacheoff +
-           TupleDescAttr(tupleDesc, j - 1)->attlen;
+       off = TupleDescCompactAttr(tupleDesc, j - 1)->attcacheoff +
+           TupleDescCompactAttr(tupleDesc, j - 1)->attlen;
 
        for (; j < natts; j++)
        {
-           Form_pg_attribute att = TupleDescAttr(tupleDesc, j);
+           CompactAttribute *att = TupleDescCompactAttr(tupleDesc, j);
 
            if (att->attlen <= 0)
                break;
@@ -372,7 +372,7 @@ nocache_index_getattr(IndexTuple tup,
 
        Assert(j > attnum);
 
-       off = TupleDescAttr(tupleDesc, attnum)->attcacheoff;
+       off = TupleDescCompactAttr(tupleDesc, attnum)->attcacheoff;
    }
    else
    {
@@ -392,7 +392,7 @@ nocache_index_getattr(IndexTuple tup,
        off = 0;
        for (i = 0;; i++)       /* loop exit is at "break" */
        {
-           Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
+           CompactAttribute *att = TupleDescCompactAttr(tupleDesc, i);
 
            if (IndexTupleHasNulls(tup) && att_isnull(i, bp))
            {
@@ -440,7 +440,7 @@ nocache_index_getattr(IndexTuple tup,
        }
    }
 
-   return fetchatt(TupleDescAttr(tupleDesc, attnum), tp + off);
+   return fetchatt(TupleDescCompactAttr(tupleDesc, attnum), tp + off);
 }
 
 /*
@@ -490,7 +490,7 @@ index_deform_tuple_internal(TupleDesc tupleDescriptor,
 
    for (attnum = 0; attnum < natts; attnum++)
    {
-       Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, attnum);
+       CompactAttribute *thisatt = TupleDescCompactAttr(tupleDescriptor, attnum);
 
        if (hasnulls && att_isnull(attnum, bp))
        {
@@ -588,7 +588,7 @@ index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source,
        return CopyIndexTuple(source);
 
    /* Create temporary descriptor to scribble on */
-   truncdesc = palloc(TupleDescSize(sourceDescriptor));
+   truncdesc = CreateTemplateTupleDesc(sourceDescriptor->natts);
    TupleDescCopy(truncdesc, sourceDescriptor);
    truncdesc->natts = leavenatts;
 
index 47379fef220b7a26f5c62f1c949af8406b268dbd..b49584768e52858c6dd895397f192d5b973690e9 100644 (file)
@@ -56,6 +56,33 @@ ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
    ResourceOwnerForget(owner, PointerGetDatum(tupdesc), &tupdesc_resowner_desc);
 }
 
+/*
+ * populate_compact_attribute
+ *     Fills in the corresponding CompactAttribute element from the
+ *     Form_pg_attribute for the given attribute number.  This must be called
+ *     whenever a change is made to a Form_pg_attribute in the TupleDesc.
+ */
+void
+populate_compact_attribute(TupleDesc tupdesc, int attnum)
+{
+   Form_pg_attribute src = TupleDescAttr(tupdesc, attnum);
+   CompactAttribute *dst = &tupdesc->compact_attrs[attnum];
+
+   memset(dst, 0, sizeof(CompactAttribute));
+
+   dst->attcacheoff = -1;
+   dst->attlen = src->attlen;
+
+   dst->attbyval = src->attbyval;
+   dst->attispackable = (src->attstorage != TYPSTORAGE_PLAIN);
+   dst->atthasmissing = src->atthasmissing;
+   dst->attisdropped = src->attisdropped;
+   dst->attgenerated = (src->attgenerated != '\0');
+   dst->attnotnull = src->attnotnull;
+
+   dst->attalign = src->attalign;
+}
+
 /*
  * CreateTemplateTupleDesc
  *     This function allocates an empty tuple descriptor structure.
@@ -74,18 +101,19 @@ CreateTemplateTupleDesc(int natts)
    Assert(natts >= 0);
 
    /*
-    * Allocate enough memory for the tuple descriptor, including the
-    * attribute rows.
+    * Allocate enough memory for the tuple descriptor, the CompactAttribute
+    * array and also an array of the full FormData_pg_attribute data.
     *
-    * Note: the attribute array stride is sizeof(FormData_pg_attribute),
-    * since we declare the array elements as FormData_pg_attribute for
-    * notational convenience.  However, we only guarantee that the first
+    * Note: the 'attrs' array stride is sizeof(FormData_pg_attribute), since
+    * we declare the array elements as FormData_pg_attribute for notational
+    * convenience.  However, we only guarantee that the first
     * ATTRIBUTE_FIXED_PART_SIZE bytes of each entry are valid; most code that
     * copies tupdesc entries around copies just that much.  In principle that
     * could be less due to trailing padding, although with the current
     * definition of pg_attribute there probably isn't any padding.
     */
-   desc = (TupleDesc) palloc(offsetof(struct TupleDescData, attrs) +
+   desc = (TupleDesc) palloc(offsetof(struct TupleDescData, compact_attrs) +
+                             natts * sizeof(CompactAttribute) +
                              natts * sizeof(FormData_pg_attribute));
 
    /*
@@ -96,6 +124,7 @@ CreateTemplateTupleDesc(int natts)
    desc->tdtypeid = RECORDOID;
    desc->tdtypmod = -1;
    desc->tdrefcount = -1;      /* assume not reference-counted */
+   desc->attrs = TupleDescAttrAddress(desc);
 
    return desc;
 }
@@ -117,8 +146,10 @@ CreateTupleDesc(int natts, Form_pg_attribute *attrs)
    desc = CreateTemplateTupleDesc(natts);
 
    for (i = 0; i < natts; ++i)
+   {
        memcpy(TupleDescAttr(desc, i), attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
-
+       populate_compact_attribute(desc, i);
+   }
    return desc;
 }
 
@@ -155,6 +186,8 @@ CreateTupleDescCopy(TupleDesc tupdesc)
        att->atthasmissing = false;
        att->attidentity = '\0';
        att->attgenerated = '\0';
+
+       populate_compact_attribute(desc, i);
    }
 
    /* We can copy the tuple type identification, too */
@@ -183,6 +216,9 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
           TupleDescAttr(tupdesc, 0),
           desc->natts * sizeof(FormData_pg_attribute));
 
+   for (i = 0; i < desc->natts; i++)
+       populate_compact_attribute(desc, i);
+
    /* Copy the TupleConstr data structure, if any */
    if (constr)
    {
@@ -207,7 +243,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
            {
                if (constr->missing[i].am_present)
                {
-                   Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+                   CompactAttribute *attr = TupleDescCompactAttr(tupdesc, i);
 
                    cpy->missing[i].am_value = datumCopy(constr->missing[i].am_value,
                                                         attr->attbyval,
@@ -252,9 +288,15 @@ TupleDescCopy(TupleDesc dst, TupleDesc src)
 {
    int         i;
 
-   /* Flat-copy the header and attribute array */
+   /* Flat-copy the header and attribute arrays */
    memcpy(dst, src, TupleDescSize(src));
 
+   /*
+    * Adjust 'attrs' to point to the dst FormData_pg_attribute array rather
+    * than the src's.
+    */
+   dst->attrs = TupleDescAttrAddress(dst);
+
    /*
     * Since we're not copying constraints and defaults, clear fields
     * associated with them.
@@ -268,6 +310,8 @@ TupleDescCopy(TupleDesc dst, TupleDesc src)
        att->atthasmissing = false;
        att->attidentity = '\0';
        att->attgenerated = '\0';
+
+       populate_compact_attribute(dst, i);
    }
    dst->constr = NULL;
 
@@ -322,6 +366,8 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
    dstAtt->atthasmissing = false;
    dstAtt->attidentity = '\0';
    dstAtt->attgenerated = '\0';
+
+   populate_compact_attribute(dst, dstAttno - 1);
 }
 
 /*
@@ -521,7 +567,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
                    return false;
                if (missval1->am_present)
                {
-                   Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, i);
+                   CompactAttribute *missatt1 = TupleDescCompactAttr(tupdesc1, i);
 
                    if (!datumIsEqual(missval1->am_value, missval2->am_value,
                                      missatt1->attbyval, missatt1->attlen))
@@ -714,6 +760,8 @@ TupleDescInitEntry(TupleDesc desc,
    att->attcompression = InvalidCompressionMethod;
    att->attcollation = typeForm->typcollation;
 
+   populate_compact_attribute(desc, attributeNumber - 1);
+
    ReleaseSysCache(tuple);
 }
 
@@ -821,6 +869,8 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
        default:
            elog(ERROR, "unsupported type %u", oidtypeid);
    }
+
+   populate_compact_attribute(desc, attributeNumber - 1);
 }
 
 /*
index e93d9869b27df58664c996e64a63cc99b5fab812..da858182173ffc90924ef1c8bdf2555dabedc2ae 100644 (file)
@@ -331,7 +331,9 @@ getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType)
        att->attcollation = InvalidOid;
        /* In case we changed typlen, we'd better reset following offsets */
        for (int i = spgFirstIncludeColumn; i < outTupDesc->natts; i++)
-           TupleDescAttr(outTupDesc, i)->attcacheoff = -1;
+           TupleDescCompactAttr(outTupDesc, i)->attcacheoff = -1;
+
+       populate_compact_attribute(outTupDesc, spgKeyColumn);
    }
    return outTupDesc;
 }
index 1c3a9e06d37b9bfce737c3259c95d6dec14dc25a..2a03a506c02c629a9b8623b02b7de4f2ac624261 100644 (file)
@@ -477,6 +477,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 
            ReleaseSysCache(tuple);
        }
+
+       populate_compact_attribute(indexTupDesc, i);
    }
 
    pfree(amroutine);
index 6ccae4cb4a8613b0a93d4b38a752c7475e92029c..49374782625c2339a84a0c5119f09ccc494d6cb3 100644 (file)
@@ -980,6 +980,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
            cookedDefaults = lappend(cookedDefaults, cooked);
            attr->atthasdef = true;
        }
+
+       populate_compact_attribute(descriptor, attnum - 1);
    }
 
    /*
@@ -1396,6 +1398,8 @@ BuildDescForRelation(const List *columns)
            att->attstorage = entry->storage;
        else if (entry->storage_name)
            att->attstorage = GetAttributeStorage(att->atttypid, entry->storage_name);
+
+       populate_compact_attribute(desc, attnum - 1);
    }
 
    return desc;
index 00dc3396156c5add54524fe950307bda3561f835..a09aa251e67c596099ab6b0afe2f7c9e6952e3fc 100644 (file)
@@ -1044,7 +1044,7 @@ slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
 
    for (; attnum < natts; attnum++)
    {
-       Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum);
+       CompactAttribute *thisatt = TupleDescCompactAttr(tupleDesc, attnum);
 
        if (hasnulls && att_isnull(attnum, bp))
        {
@@ -2237,7 +2237,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values)
     */
    for (i = 0; i < natts; i++)
    {
-       if (!TupleDescAttr(tupdesc, i)->attisdropped)
+       if (!TupleDescCompactAttr(tupdesc, i)->attisdropped)
        {
            /* Non-dropped attributes */
            dvalues[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
index d0892cee24dbfaf550f1fb76a42a335f660043f9..e230450d2c32dce6425730f96aea5ac14ed414ed 100644 (file)
@@ -585,6 +585,8 @@ RelationBuildTupleDesc(Relation relation)
               attp,
               ATTRIBUTE_FIXED_PART_SIZE);
 
+       populate_compact_attribute(relation->rd_att, attnum - 1);
+
        /* Update constraint/default info */
        if (attp->attnotnull)
            constr->has_not_null = true;
@@ -674,12 +676,12 @@ RelationBuildTupleDesc(Relation relation)
 #endif
 
    /*
-    * However, we can easily set the attcacheoff value for the first
-    * attribute: it must be zero.  This eliminates the need for special cases
-    * for attnum=1 that used to exist in fastgetattr() and index_getattr().
+    * We can easily set the attcacheoff value for the first attribute: it
+    * must be zero.  This eliminates the need for special cases for attnum=1
+    * that used to exist in fastgetattr() and index_getattr().
     */
    if (RelationGetNumberOfAttributes(relation) > 0)
-       TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
+       TupleDescCompactAttr(relation->rd_att, 0)->attcacheoff = 0;
 
    /*
     * Set up constraint/default info
@@ -1965,10 +1967,12 @@ formrdesc(const char *relationName, Oid relationReltype,
        has_not_null |= attrs[i].attnotnull;
        /* make sure attcacheoff is valid */
        TupleDescAttr(relation->rd_att, i)->attcacheoff = -1;
+
+       populate_compact_attribute(relation->rd_att, i);
    }
 
    /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
-   TupleDescAttr(relation->rd_att, 0)->attcacheoff = 0;
+   TupleDescCompactAttr(relation->rd_att, 0)->attcacheoff = 0;
 
    /* mark not-null status */
    if (has_not_null)
@@ -3579,6 +3583,7 @@ RelationBuildLocalRelation(const char *relname,
        datt->attgenerated = satt->attgenerated;
        datt->attnotnull = satt->attnotnull;
        has_not_null |= satt->attnotnull;
+       populate_compact_attribute(rel->rd_att, i);
    }
 
    if (has_not_null)
@@ -4399,10 +4404,12 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs)
        memcpy(TupleDescAttr(result, i), &attrs[i], ATTRIBUTE_FIXED_PART_SIZE);
        /* make sure attcacheoff is valid */
        TupleDescAttr(result, i)->attcacheoff = -1;
+
+       populate_compact_attribute(result, i);
    }
 
    /* initialize first attribute's attcacheoff, cf RelationBuildTupleDesc */
-   TupleDescAttr(result, 0)->attcacheoff = 0;
+   TupleDescCompactAttr(result, 0)->attcacheoff = 0;
 
    /* Note: we don't bother to set up a TupleConstr entry */
 
@@ -6166,6 +6173,8 @@ load_relcache_init_file(bool shared)
                goto read_failed;
 
            has_not_null |= attr->attnotnull;
+
+           populate_compact_attribute(rel->rd_att, i);
        }
 
        /* next read the access method specific field */
index 6590cbe5c67a44325d223bde249e17efb8db7dad..8ce32a475f01886708db5acee3de31dee16b128e 100644 (file)
@@ -241,12 +241,18 @@ shared_record_table_compare(const void *a, const void *b, size_t size,
    TupleDesc   t2;
 
    if (k1->shared)
+   {
        t1 = (TupleDesc) dsa_get_address(area, k1->u.shared_tupdesc);
+       t1->attrs = TupleDescAttrAddress(t1);
+   }
    else
        t1 = k1->u.local_tupdesc;
 
    if (k2->shared)
+   {
        t2 = (TupleDesc) dsa_get_address(area, k2->u.shared_tupdesc);
+       t2->attrs = TupleDescAttrAddress(t2);
+   }
    else
        t2 = k2->u.local_tupdesc;
 
@@ -264,7 +270,10 @@ shared_record_table_hash(const void *a, size_t size, void *arg)
    TupleDesc   t;
 
    if (k->shared)
+   {
        t = (TupleDesc) dsa_get_address(area, k->u.shared_tupdesc);
+       t->attrs = TupleDescAttrAddress(t);
+   }
    else
        t = k->u.local_tupdesc;
 
@@ -1867,6 +1876,7 @@ lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
                    tupdesc = (TupleDesc)
                        dsa_get_address(CurrentSession->area,
                                        entry->shared_tupdesc);
+                   tupdesc->attrs = TupleDescAttrAddress(tupdesc);
                    Assert(typmod == tupdesc->tdtypmod);
 
                    /* We may need to extend the local RecordCacheArray. */
@@ -2953,6 +2963,7 @@ find_or_make_matching_shared_tupledesc(TupleDesc tupdesc)
        result = (TupleDesc)
            dsa_get_address(CurrentSession->area,
                            record_table_entry->key.u.shared_tupdesc);
+       result->attrs = TupleDescAttrAddress(result);
        Assert(result->tdrefcount == -1);
 
        return result;
@@ -3016,6 +3027,7 @@ find_or_make_matching_shared_tupledesc(TupleDesc tupdesc)
        result = (TupleDesc)
            dsa_get_address(CurrentSession->area,
                            record_table_entry->key.u.shared_tupdesc);
+       result->attrs = TupleDescAttrAddress(result);
        Assert(result->tdrefcount == -1);
 
        return result;
@@ -3028,6 +3040,7 @@ find_or_make_matching_shared_tupledesc(TupleDesc tupdesc)
                        record_table_entry);
    result = (TupleDesc)
        dsa_get_address(CurrentSession->area, shared_dp);
+   result->attrs = TupleDescAttrAddress(result);
    Assert(result->tdrefcount == -1);
 
    return result;
index 5e38ef869697ace7567dec9c648bfb4884f8b6a7..0d1adff540f7b9c4239aa2594ff5fdac2262a56a 100644 (file)
@@ -758,9 +758,9 @@ fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
    *isnull = false;
    if (HeapTupleNoNulls(tup))
    {
-       Form_pg_attribute att;
+       CompactAttribute *att;
 
-       att = TupleDescAttr(tupleDesc, attnum - 1);
+       att = TupleDescCompactAttr(tupleDesc, attnum - 1);
        if (att->attcacheoff >= 0)
            return fetchatt(att, (char *) tup->t_data + tup->t_data->t_hoff +
                            att->attcacheoff);
index 94885751e590f93eba33dc7ebfd198de9df047c2..4393b19a7fdecd04a7a9bc7de3f5d260a65d4575 100644 (file)
@@ -124,11 +124,13 @@ index_getattr(IndexTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
 
    if (!IndexTupleHasNulls(tup))
    {
-       if (TupleDescAttr(tupleDesc, attnum - 1)->attcacheoff >= 0)
+       CompactAttribute *attr = TupleDescCompactAttr(tupleDesc, attnum - 1);
+
+       if (attr->attcacheoff >= 0)
        {
-           return fetchatt(TupleDescAttr(tupleDesc, attnum - 1),
-                           (char *) tup + IndexInfoFindDataOffset(tup->t_info)
-                           + TupleDescAttr(tupleDesc, attnum - 1)->attcacheoff);
+           return fetchatt(attr,
+                           (char *) tup + IndexInfoFindDataOffset(tup->t_info) +
+                           attr->attcacheoff);
        }
        else
            return nocache_index_getattr(tup, attnum, tupleDesc);
index 8930a28d6602eaa37120b33b51549122a6357a1c..bca2ae8afb3bf5bffb7db90701565092708dbf62 100644 (file)
@@ -45,6 +45,39 @@ typedef struct TupleConstr
    bool        has_generated_stored;
 } TupleConstr;
 
+/*
+ * CompactAttribute
+ *     Cut-down version of FormData_pg_attribute for faster access for tasks
+ *     such as tuple deformation.  These values are populated using the
+ *     populate_compact_attribute function, which must be called directly
+ *     after the FormData_pg_attribute struct is populated or altered in any
+ *     way.
+ *
+ * Currently, this struct is 16 bytes.  Any code changes which enlarge this
+ * struct should be considered very carefully.
+ *
+ * Code which must access a TupleDesc's attribute data should always make use
+ * of the CompactAttribute when the required fields are available there.  It's
+ * more efficient to access the memory in CompactAttribute due to it both
+ * being a more compact representation of FormData_pg_attribute, but also
+ * because accessing the FormData_pg_attribute requires an additional pointer
+ * indirection through TupleDescData.attrs
+ */
+typedef struct CompactAttribute
+{
+   int32       attcacheoff;    /* fixed offset into tuple, if known, or -1 */
+   int16       attlen;         /* attr len in bytes or -1 = varlen, -2 =
+                                * cstring */
+   bool        attbyval;       /* as FormData_pg_attribute.attbyval */
+   bool        attispackable;  /* FormData_pg_attribute.attstorage !=
+                                * TYPSTORAGE_PLAIN */
+   bool        atthasmissing;  /* as FormData_pg_attribute.atthasmissing */
+   bool        attisdropped;   /* as FormData_pg_attribute.attisdropped */
+   bool        attgenerated;   /* FormData_pg_attribute.attgenerated != '\0' */
+   bool        attnotnull;     /* as FormData_pg_attribute.attnotnull */
+   char        attalign;       /* alignment requirement */
+} CompactAttribute;
+
 /*
  * This struct is passed around within the backend to describe the structure
  * of tuples.  For tuples coming from on-disk relations, the information is
@@ -75,6 +108,18 @@ typedef struct TupleConstr
  * context and go away when the context is freed.  We set the tdrefcount
  * field of such a descriptor to -1, while reference-counted descriptors
  * always have tdrefcount >= 0.
+ *
+ * The attrs field stores the fixed-sized portion of FormData_pg_attribute.
+ * Because that struct is large, we also store a corresponding
+ * CompactAttribute for each attribute in compact_attrs.  compact_attrs is
+ * stored inline with the struct.  Because CompactAttribute is significantly
+ * smaller than FormData_pg_attribute, code, especially performance-critical
+ * code, should prioritize using the fields from the CompactAttribute over the
+ * equivalent fields in FormData_pg_attribute whenever possible.
+ *
+ * Any code making changes manually to the fields in 'attrs' must subsequently
+ * call populate_compact_attribute() to flush the changes out to the
+ * corresponding 'compact_attrs' element.
  */
 typedef struct TupleDescData
 {
@@ -84,13 +129,53 @@ typedef struct TupleDescData
    int         tdrefcount;     /* reference count, or -1 if not counting */
    TupleConstr *constr;        /* constraints, or NULL if none */
    /* attrs[N] is the description of Attribute Number N+1 */
-   FormData_pg_attribute attrs[FLEXIBLE_ARRAY_MEMBER];
+   FormData_pg_attribute *attrs;
+   CompactAttribute compact_attrs[FLEXIBLE_ARRAY_MEMBER];
 }          TupleDescData;
 typedef struct TupleDescData *TupleDesc;
 
-/* Accessor for the i'th attribute of tupdesc. */
+extern void populate_compact_attribute(TupleDesc tupdesc, int attnum);
+
+/* Accessor for the i'th FormData_pg_attribute of tupdesc. */
 #define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)])
 
+/*
+ * Accessor for the i'th CompactAttribute of tupdesc.
+ */
+static inline CompactAttribute *
+TupleDescCompactAttr(TupleDesc tupdesc, int i)
+{
+   CompactAttribute *cattr = &tupdesc->compact_attrs[i];
+#ifdef USE_ASSERT_CHECKING
+   CompactAttribute snapshot;
+
+   /*
+    * In Assert enabled builds we verify that the CompactAttribute is
+    * populated correctly.  This helps find bugs in places such as ALTER
+    * TABLE where code makes changes to the FormData_pg_attribute but forgets
+    * to call populate_compact_attribute.
+    */
+
+   /*
+    * Take a snapshot of how the CompactAttribute is now before calling
+    * populate_compact_attribute to make it up-to-date with the
+    * FormData_pg_attribute.
+    */
+   memcpy(&snapshot, cattr, sizeof(CompactAttribute));
+
+   populate_compact_attribute(tupdesc, i);
+
+   /* reset attcacheoff back to what it was */
+   cattr->attcacheoff = snapshot.attcacheoff;
+
+   /* Ensure the snapshot matches the freshly populated CompactAttribute */
+   Assert(memcmp(&snapshot, cattr, sizeof(CompactAttribute)) == 0);
+#endif
+
+   return cattr;
+}
+
+
 extern TupleDesc CreateTemplateTupleDesc(int natts);
 
 extern TupleDesc CreateTupleDesc(int natts, Form_pg_attribute *attrs);
@@ -100,9 +185,15 @@ extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc);
 extern TupleDesc CreateTupleDescCopyConstr(TupleDesc tupdesc);
 
 #define TupleDescSize(src) \
-   (offsetof(struct TupleDescData, attrs) + \
+   (offsetof(struct TupleDescData, compact_attrs) + \
+    (src)->natts * sizeof(CompactAttribute) + \
     (src)->natts * sizeof(FormData_pg_attribute))
 
+#define TupleDescAttrAddress(desc) \
+   (Form_pg_attribute) ((char *) (desc) + \
+    (offsetof(struct TupleDescData, compact_attrs) + \
+    (desc)->natts * sizeof(CompactAttribute)))
+
 extern void TupleDescCopy(TupleDesc dst, TupleDesc src);
 
 extern void TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
index 58b3a58cfd06c064a2071609ca43ba1ea9eaf167..622adfa5f86512a572564cd0d0a87995c6fa3f11 100644 (file)
@@ -14,6 +14,7 @@
 #ifndef TUPMACS_H
 #define TUPMACS_H
 
+#include "access/tupdesc.h"
 #include "catalog/pg_type_d.h" /* for TYPALIGN macros */
 
 
@@ -30,8 +31,8 @@ att_isnull(int ATT, const bits8 *BITS)
 
 #ifndef FRONTEND
 /*
- * Given a Form_pg_attribute and a pointer into a tuple's data area,
- * return the correct value or pointer.
+ * Given a Form_pg_attribute or CompactAttribute and a pointer into a tuple's
+ * data area, return the correct value or pointer.
  *
  * We return a Datum value in all cases.  If the attribute has "byval" false,
  * we return the same pointer into the tuple data area that we're passed.
index 2d4c870423acfe401060dd2e1fcbe3b7c1298165..dd5c8867d239c7bfcc510096a000e9527ccd80a7 100644 (file)
@@ -454,6 +454,7 @@ CommitTimestampEntry
 CommitTimestampShared
 CommonEntry
 CommonTableExpr
+CompactAttribute
 CompareScalarsContext
 CompiledExprState
 CompositeIOData