Skip to content

Commit 80b9e9c

Browse files
committed
Improve performance of index-only scans with many index columns.
StoreIndexTuple was a loop over index_getattr, which is O(N^2) if the index columns are variable-width, and the performance impact is already quite visible at ten columns. The obvious move is to replace that with a call to index_deform_tuple ... but that's *also* a loop over index_getattr. Improve it to be essentially a clone of heap_deform_tuple. (There are a few other places that loop over all index columns with index_getattr, and perhaps should be changed likewise, but most of them don't seem performance-critical. Anyway, the rest would mostly only be interested in the index key columns, which there aren't likely to be so many of. Wide index tuples are a new thing with INCLUDE.) Konstantin Knizhnik Discussion: https://postgr.es/m/e06b2d27-04fc-5c0e-bb8c-ecd72aa24959@postgrespro.ru
1 parent 78b408a commit 80b9e9c

File tree

2 files changed

+72
-17
lines changed

2 files changed

+72
-17
lines changed

src/backend/access/common/indextuple.c

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,19 +418,80 @@ nocache_index_getattr(IndexTuple tup,
418418
*
419419
* The caller must allocate sufficient storage for the output arrays.
420420
* (INDEX_MAX_KEYS entries should be enough.)
421+
*
422+
* This is nearly the same as heap_deform_tuple(), but for IndexTuples.
423+
* One difference is that the tuple should never have any missing columns.
421424
*/
422425
void
423426
index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
424427
Datum *values, bool *isnull)
425428
{
426-
int i;
429+
int hasnulls = IndexTupleHasNulls(tup);
430+
int natts = tupleDescriptor->natts; /* number of atts to extract */
431+
int attnum;
432+
char *tp; /* ptr to tuple data */
433+
int off; /* offset in tuple data */
434+
bits8 *bp; /* ptr to null bitmap in tuple */
435+
bool slow = false; /* can we use/set attcacheoff? */
427436

428437
/* Assert to protect callers who allocate fixed-size arrays */
429-
Assert(tupleDescriptor->natts <= INDEX_MAX_KEYS);
438+
Assert(natts <= INDEX_MAX_KEYS);
439+
440+
/* XXX "knows" t_bits are just after fixed tuple header! */
441+
bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
430442

431-
for (i = 0; i < tupleDescriptor->natts; i++)
443+
tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
444+
off = 0;
445+
446+
for (attnum = 0; attnum < natts; attnum++)
432447
{
433-
values[i] = index_getattr(tup, i + 1, tupleDescriptor, &isnull[i]);
448+
Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, attnum);
449+
450+
if (hasnulls && att_isnull(attnum, bp))
451+
{
452+
values[attnum] = (Datum) 0;
453+
isnull[attnum] = true;
454+
slow = true; /* can't use attcacheoff anymore */
455+
continue;
456+
}
457+
458+
isnull[attnum] = false;
459+
460+
if (!slow && thisatt->attcacheoff >= 0)
461+
off = thisatt->attcacheoff;
462+
else if (thisatt->attlen == -1)
463+
{
464+
/*
465+
* We can only cache the offset for a varlena attribute if the
466+
* offset is already suitably aligned, so that there would be no
467+
* pad bytes in any case: then the offset will be valid for either
468+
* an aligned or unaligned value.
469+
*/
470+
if (!slow &&
471+
off == att_align_nominal(off, thisatt->attalign))
472+
thisatt->attcacheoff = off;
473+
else
474+
{
475+
off = att_align_pointer(off, thisatt->attalign, -1,
476+
tp + off);
477+
slow = true;
478+
}
479+
}
480+
else
481+
{
482+
/* not varlena, so safe to use att_align_nominal */
483+
off = att_align_nominal(off, thisatt->attalign);
484+
485+
if (!slow)
486+
thisatt->attcacheoff = off;
487+
}
488+
489+
values[attnum] = fetchatt(thisatt, tp + off);
490+
491+
off = att_addlength_pointer(off, thisatt->attlen, tp + off);
492+
493+
if (thisatt->attlen <= 0)
494+
slow = true; /* can't use attcacheoff anymore */
434495
}
435496
}
436497

src/backend/executor/nodeIndexonlyscan.c

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,23 +269,17 @@ IndexOnlyNext(IndexOnlyScanState *node)
269269
static void
270270
StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc)
271271
{
272-
int nindexatts = itupdesc->natts;
273-
Datum *values = slot->tts_values;
274-
bool *isnull = slot->tts_isnull;
275-
int i;
276-
277272
/*
278-
* Note: we must use the tupdesc supplied by the AM in index_getattr, not
279-
* the slot's tupdesc, in case the latter has different datatypes (this
280-
* happens for btree name_ops in particular). They'd better have the same
281-
* number of columns though, as well as being datatype-compatible which is
282-
* something we can't so easily check.
273+
* Note: we must use the tupdesc supplied by the AM in index_deform_tuple,
274+
* not the slot's tupdesc, in case the latter has different datatypes
275+
* (this happens for btree name_ops in particular). They'd better have
276+
* the same number of columns though, as well as being datatype-compatible
277+
* which is something we can't so easily check.
283278
*/
284-
Assert(slot->tts_tupleDescriptor->natts == nindexatts);
279+
Assert(slot->tts_tupleDescriptor->natts == itupdesc->natts);
285280

286281
ExecClearTuple(slot);
287-
for (i = 0; i < nindexatts; i++)
288-
values[i] = index_getattr(itup, i + 1, itupdesc, &isnull[i]);
282+
index_deform_tuple(itup, itupdesc, slot->tts_values, slot->tts_isnull);
289283
ExecStoreVirtualTuple(slot);
290284
}
291285

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy