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
This commit is contained in:
David Rowley
2024-12-03 16:50:59 +13:00
parent e4c8865196
commit d28dff3f6c
14 changed files with 246 additions and 68 deletions

View 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);

View 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);

View 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,

View 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.