From 543fa66050ef98f5c092500f93a24b0409c03bb1 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Tue, 3 Dec 2024 16:50:59 +1300 Subject: [PATCH v7 1/2] Introduce CompactAttribute array in TupleDesc 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 --- contrib/pageinspect/gistfuncs.c | 4 +- src/backend/access/common/heaptuple.c | 53 +++++------ src/backend/access/common/indextuple.c | 30 +++---- src/backend/access/common/tupdesc.c | 109 +++++++++++++++++++--- src/backend/access/gist/gist.c | 5 +- src/backend/access/spgist/spgutils.c | 4 +- src/backend/catalog/index.c | 2 + src/backend/commands/tablecmds.c | 4 + src/backend/executor/execTuples.c | 4 +- src/backend/utils/cache/relcache.c | 21 +++-- src/include/access/htup_details.h | 4 +- src/include/access/itup.h | 10 ++- src/include/access/tupdesc.h | 120 +++++++++++++++++++++++-- src/include/access/tupmacs.h | 4 +- src/tools/pgindent/typedefs.list | 1 + 15 files changed, 297 insertions(+), 78 deletions(-) diff --git a/contrib/pageinspect/gistfuncs.c b/contrib/pageinspect/gistfuncs.c index 029072567a..68d59cb3ec 100644 --- a/contrib/pageinspect/gistfuncs.c +++ b/contrib/pageinspect/gistfuncs.c @@ -242,8 +242,8 @@ gist_page_items(PG_FUNCTION_ARGS) } else { - tupdesc = CreateTupleDescCopy(RelationGetDescr(indexRel)); - tupdesc->natts = IndexRelationGetNumberOfKeyAttributes(indexRel); + tupdesc = CreateTupleDescTruncatedCopy(RelationGetDescr(indexRel), + IndexRelationGetNumberOfKeyAttributes(indexRel)); printflags |= RULE_INDEXDEF_KEYS_ONLY; } diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 9e3407bf98..982e7222c4 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -83,6 +83,10 @@ #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)) { diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c index bb2c6a2bcc..38aeb23087 100644 --- a/src/backend/access/common/indextuple.c +++ b/src/backend/access/common/indextuple.c @@ -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)) { @@ -587,10 +587,8 @@ index_truncate_tuple(TupleDesc sourceDescriptor, IndexTuple source, if (leavenatts == sourceDescriptor->natts) return CopyIndexTuple(source); - /* Create temporary descriptor to scribble on */ - truncdesc = palloc(TupleDescSize(sourceDescriptor)); - TupleDescCopy(truncdesc, sourceDescriptor); - truncdesc->natts = leavenatts; + /* Create temporary truncated tuple descriptor */ + truncdesc = CreateTupleDescTruncatedCopy(sourceDescriptor, leavenatts); /* Deform, form copy of tuple with fewer attributes */ index_deform_tuple(source, truncdesc, values, isnull); diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index 47379fef22..a9fd227af1 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -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)); /* @@ -117,8 +145,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 +185,54 @@ 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 */ + desc->tdtypeid = tupdesc->tdtypeid; + desc->tdtypmod = tupdesc->tdtypmod; + + return desc; +} + +/* + * CreateTupleDescTruncatedCopy + * This function creates a new TupleDesc with only the first 'natts' + * attributes from an existing TupleDesc + * + * !!! Constraints and defaults are not copied !!! + */ +TupleDesc +CreateTupleDescTruncatedCopy(TupleDesc tupdesc, int natts) +{ + TupleDesc desc; + int i; + + Assert(natts <= tupdesc->natts); + + desc = CreateTemplateTupleDesc(natts); + + /* Flat-copy the attribute array */ + memcpy(TupleDescAttr(desc, 0), + TupleDescAttr(tupdesc, 0), + desc->natts * sizeof(FormData_pg_attribute)); + + /* + * Since we're not copying constraints and defaults, clear fields + * associated with them. + */ + for (i = 0; i < desc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(desc, i); + + att->attnotnull = false; + att->atthasdef = false; + att->atthasmissing = false; + att->attidentity = '\0'; + att->attgenerated = '\0'; + + populate_compact_attribute(desc, i); } /* We can copy the tuple type identification, too */ @@ -183,6 +261,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 +288,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,7 +333,7 @@ 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)); /* @@ -268,6 +349,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 +405,8 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, dstAtt->atthasmissing = false; dstAtt->attidentity = '\0'; dstAtt->attgenerated = '\0'; + + populate_compact_attribute(dst, dstAttno - 1); } /* @@ -521,7 +606,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 +799,8 @@ TupleDescInitEntry(TupleDesc desc, att->attcompression = InvalidCompressionMethod; att->attcollation = typeForm->typcollation; + populate_compact_attribute(desc, attributeNumber - 1); + ReleaseSysCache(tuple); } @@ -821,6 +908,8 @@ TupleDescInitBuiltinEntry(TupleDesc desc, default: elog(ERROR, "unsupported type %u", oidtypeid); } + + populate_compact_attribute(desc, attributeNumber - 1); } /* diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index ebc65449c1..272390ff67 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -1557,9 +1557,8 @@ initGISTstate(Relation index) * tuples during page split. Also, B-tree is not adjusting tuples on * internal pages the way GiST does. */ - giststate->nonLeafTupdesc = CreateTupleDescCopyConstr(index->rd_att); - giststate->nonLeafTupdesc->natts = - IndexRelationGetNumberOfKeyAttributes(index); + giststate->nonLeafTupdesc = CreateTupleDescTruncatedCopy(index->rd_att, + IndexRelationGetNumberOfKeyAttributes(index)); for (i = 0; i < IndexRelationGetNumberOfKeyAttributes(index); i++) { diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index e93d9869b2..da85818217 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -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; } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 1c3a9e06d3..2a03a506c0 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -477,6 +477,8 @@ ConstructTupleDescriptor(Relation heapRelation, ReleaseSysCache(tuple); } + + populate_compact_attribute(indexTupDesc, i); } pfree(amroutine); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6ccae4cb4a..4937478262 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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; diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 00dc339615..a09aa251e6 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -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], diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 422509f18d..f6686a69e8 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -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 */ @@ -6199,6 +6206,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 */ diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index 5e38ef8696..0d1adff540 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -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); diff --git a/src/include/access/itup.h b/src/include/access/itup.h index 94885751e5..4393b19a7f 100644 --- a/src/include/access/itup.h +++ b/src/include/access/itup.h @@ -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); diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 8930a28d66..21d5ba94ad 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -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 being a + * more compact representation of FormData_pg_attribute and also because + * accessing the FormData_pg_attribute requires an additional calculations to + * obtain the base address of the array within the TupleDesc. + */ +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,21 @@ 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. + * + * Beyond the compact_attrs variable length array, the TupleDesc stores an + * array of FormData_pg_attribute. The TupleDescAttr() function, as defined + * below, takes care of calculating the address of the elements of the + * FormData_pg_attribute array. + * + * The array of CompactAttribute is effectively an abbreviated version of the + * array of FormData_pg_attribute. 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 and fields in the FormData_pg_attribute + * array must subsequently call populate_compact_attribute() to flush the + * changes out to the corresponding 'compact_attrs' element. */ typedef struct TupleDescData { @@ -83,13 +131,72 @@ typedef struct TupleDescData int32 tdtypmod; /* typmod for tuple type */ 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]; + /* compact_attrs[N] is the compact metadata of Attribute Number N+1 */ + CompactAttribute compact_attrs[FLEXIBLE_ARRAY_MEMBER]; } TupleDescData; typedef struct TupleDescData *TupleDesc; -/* Accessor for the i'th attribute of tupdesc. */ -#define TupleDescAttr(tupdesc, i) (&(tupdesc)->attrs[(i)]) +extern void populate_compact_attribute(TupleDesc tupdesc, int attnum); + +#define TupleDescAttrAddress(desc) \ + (Form_pg_attribute) ((char *) (desc) + \ + (offsetof(struct TupleDescData, compact_attrs) + \ + (desc)->natts * sizeof(CompactAttribute))) + +/* Accessor for the i'th FormData_pg_attribute of tupdesc. */ +static inline FormData_pg_attribute * +TupleDescAttr(TupleDesc tupdesc, int i) +{ + /* + * Manually calculate the base address of the FormData_pg_attribute array + * then return a pointer to the i'th element. We have lots of code which + * loops over each FormData_pg_attribute and most compilers seem to be + * able to optimize the code to have it so the base address is calculated + * only once. + */ + FormData_pg_attribute *attrs = TupleDescAttrAddress(tupdesc); + + return &attrs[i]; +} + +#undef TupleDescAttrAddress + +/* + * 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); @@ -97,10 +204,13 @@ extern TupleDesc CreateTupleDesc(int natts, Form_pg_attribute *attrs); extern TupleDesc CreateTupleDescCopy(TupleDesc tupdesc); +extern TupleDesc CreateTupleDescTruncatedCopy(TupleDesc tupdesc, int natts); + 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)) extern void TupleDescCopy(TupleDesc dst, TupleDesc src); diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h index 58b3a58cfd..51616e5724 100644 --- a/src/include/access/tupmacs.h +++ b/src/include/access/tupmacs.h @@ -30,8 +30,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. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 2d4c870423..dd5c8867d2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -454,6 +454,7 @@ CommitTimestampEntry CommitTimestampShared CommonEntry CommonTableExpr +CompactAttribute CompareScalarsContext CompiledExprState CompositeIOData -- 2.34.1