From 3a3b346c80bfafb094f296c2ab15c86063deb2b5 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent Date: Thu, 21 Apr 2022 16:22:07 +0200 Subject: [PATCH v5 7/8] Add specialization to btree index creation. This was an oversight that is corrected easily; but an oversight nonetheless. This increases the (re)build performance of indexes by another few percents. --- src/backend/utils/sort/tuplesort.c | 147 ++--------------------- src/backend/utils/sort/tuplesort_nbts.h | 148 ++++++++++++++++++++++++ src/include/access/nbtree.h | 18 +++ 3 files changed, 175 insertions(+), 138 deletions(-) create mode 100644 src/backend/utils/sort/tuplesort_nbts.h diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index 7f398bd4eb..3b96e8d4f8 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -655,8 +655,6 @@ static void writetup_cluster(Tuplesortstate *state, LogicalTape *tape, SortTuple *stup); static void readtup_cluster(Tuplesortstate *state, SortTuple *stup, LogicalTape *tape, unsigned int len); -static int comparetup_index_btree(const SortTuple *a, const SortTuple *b, - Tuplesortstate *state); static int comparetup_index_hash(const SortTuple *a, const SortTuple *b, Tuplesortstate *state); static void copytup_index(Tuplesortstate *state, SortTuple *stup, void *tup); @@ -679,6 +677,10 @@ static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup); static void tuplesort_free(Tuplesortstate *state); static void tuplesort_updatemax(Tuplesortstate *state); +#define NBT_SPECIALIZE_FILE "../../backend/utils/sort/tuplesort_nbts.h" +#include "access/nbtree_specialize.h" +#undef NBT_SPECIALIZE_FILE + /* * Specialized comparators that we can inline into specialized sorts. The goal * is to try to sort two tuples without having to follow the pointers to the @@ -1239,7 +1241,7 @@ tuplesort_begin_index_btree(Relation heapRel, sortopt & TUPLESORT_RANDOMACCESS, PARALLEL_SORT(state)); - state->comparetup = comparetup_index_btree; + state->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel); state->copytup = copytup_index; state->writetup = writetup_index; state->readtup = readtup_index; @@ -1357,7 +1359,7 @@ tuplesort_begin_index_gist(Relation heapRel, state->nKeys = IndexRelationGetNumberOfKeyAttributes(indexRel); - state->comparetup = comparetup_index_btree; + state->comparetup = NBT_SPECIALIZE_NAME(comparetup_index_btree, indexRel); state->copytup = copytup_index; state->writetup = writetup_index; state->readtup = readtup_index; @@ -4321,142 +4323,11 @@ readtup_cluster(Tuplesortstate *state, SortTuple *stup, * The btree and hash cases require separate comparison functions, but the * IndexTuple representation is the same so the copy/write/read support * functions can be shared. + * + * nbtree function can be found in tuplesort_nbts.h, and is included + * through the nbtree specialization functions. */ -static int -comparetup_index_btree(const SortTuple *a, const SortTuple *b, - Tuplesortstate *state) -{ - /* - * This is similar to comparetup_heap(), but expects index tuples. There - * is also special handling for enforcing uniqueness, and special - * treatment for equal keys at the end. - */ - SortSupport sortKey = state->sortKeys; - IndexTuple tuple1; - IndexTuple tuple2; - int keysz; - TupleDesc tupDes; - bool equal_hasnull = false; - int nkey; - int32 compare; - Datum datum1, - datum2; - bool isnull1, - isnull2; - - - /* Compare the leading sort key */ - compare = ApplySortComparator(a->datum1, a->isnull1, - b->datum1, b->isnull1, - sortKey); - if (compare != 0) - return compare; - - /* Compare additional sort keys */ - tuple1 = (IndexTuple) a->tuple; - tuple2 = (IndexTuple) b->tuple; - keysz = state->nKeys; - tupDes = RelationGetDescr(state->indexRel); - - if (sortKey->abbrev_converter) - { - datum1 = index_getattr(tuple1, 1, tupDes, &isnull1); - datum2 = index_getattr(tuple2, 1, tupDes, &isnull2); - - compare = ApplySortAbbrevFullComparator(datum1, isnull1, - datum2, isnull2, - sortKey); - if (compare != 0) - return compare; - } - - /* they are equal, so we only need to examine one null flag */ - if (a->isnull1) - equal_hasnull = true; - - sortKey++; - for (nkey = 2; nkey <= keysz; nkey++, sortKey++) - { - datum1 = index_getattr(tuple1, nkey, tupDes, &isnull1); - datum2 = index_getattr(tuple2, nkey, tupDes, &isnull2); - - compare = ApplySortComparator(datum1, isnull1, - datum2, isnull2, - sortKey); - if (compare != 0) - return compare; /* done when we find unequal attributes */ - - /* they are equal, so we only need to examine one null flag */ - if (isnull1) - equal_hasnull = true; - } - - /* - * If btree has asked us to enforce uniqueness, complain if two equal - * tuples are detected (unless there was at least one NULL field and NULLS - * NOT DISTINCT was not set). - * - * It is sufficient to make the test here, because if two tuples are equal - * they *must* get compared at some stage of the sort --- otherwise the - * sort algorithm wouldn't have checked whether one must appear before the - * other. - */ - if (state->enforceUnique && !(!state->uniqueNullsNotDistinct && equal_hasnull)) - { - Datum values[INDEX_MAX_KEYS]; - bool isnull[INDEX_MAX_KEYS]; - char *key_desc; - - /* - * Some rather brain-dead implementations of qsort (such as the one in - * QNX 4) will sometimes call the comparison routine to compare a - * value to itself, but we always use our own implementation, which - * does not. - */ - Assert(tuple1 != tuple2); - - index_deform_tuple(tuple1, tupDes, values, isnull); - - key_desc = BuildIndexValueDescription(state->indexRel, values, isnull); - - ereport(ERROR, - (errcode(ERRCODE_UNIQUE_VIOLATION), - errmsg("could not create unique index \"%s\"", - RelationGetRelationName(state->indexRel)), - key_desc ? errdetail("Key %s is duplicated.", key_desc) : - errdetail("Duplicate keys exist."), - errtableconstraint(state->heapRel, - RelationGetRelationName(state->indexRel)))); - } - - /* - * If key values are equal, we sort on ItemPointer. This is required for - * btree indexes, since heap TID is treated as an implicit last key - * attribute in order to ensure that all keys in the index are physically - * unique. - */ - { - BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid); - BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid); - - if (blk1 != blk2) - return (blk1 < blk2) ? -1 : 1; - } - { - OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid); - OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid); - - if (pos1 != pos2) - return (pos1 < pos2) ? -1 : 1; - } - - /* ItemPointer values should never be equal */ - Assert(false); - - return 0; -} - static int comparetup_index_hash(const SortTuple *a, const SortTuple *b, Tuplesortstate *state) diff --git a/src/backend/utils/sort/tuplesort_nbts.h b/src/backend/utils/sort/tuplesort_nbts.h new file mode 100644 index 0000000000..d1b2670747 --- /dev/null +++ b/src/backend/utils/sort/tuplesort_nbts.h @@ -0,0 +1,148 @@ +#ifndef NBTS_SPECIALIZING_DEFAULT + +static int NBTS_FUNCTION(comparetup_index_btree)(const SortTuple *a, + const SortTuple *b, + Tuplesortstate *state); + +static int +NBTS_FUNCTION(comparetup_index_btree)(const SortTuple *a, const SortTuple *b, + Tuplesortstate *state) +{ + /* + * This is similar to comparetup_heap(), but expects index tuples. There + * is also special handling for enforcing uniqueness, and special + * treatment for equal keys at the end. + */ + SortSupport sortKey = state->sortKeys; + IndexTuple tuple1; + IndexTuple tuple2; + int keysz; + TupleDesc tupDes; + bool equal_hasnull = false; + int nkey; + int32 compare; + nbts_attiterdeclare(tuple1); + nbts_attiterdeclare(tuple2); + + /* Compare the leading sort key */ + compare = ApplySortComparator(a->datum1, a->isnull1, + b->datum1, b->isnull1, + sortKey); + if (compare != 0) + return compare; + + /* Compare additional sort keys */ + tuple1 = (IndexTuple) a->tuple; + tuple2 = (IndexTuple) b->tuple; + keysz = state->nKeys; + tupDes = RelationGetDescr(state->indexRel); + + if (!sortKey->abbrev_converter) + { + nkey = 2; + sortKey++; + } + else + nkey = 1; + + if (a->isnull1) + equal_hasnull = true; + + nbts_attiterinit(tuple1, nkey, tupDes); + nbts_attiterinit(tuple2, nkey, tupDes); + + nbts_foreachattr(nkey, keysz) + { + Datum datum1, + datum2; + datum1 = nbts_attiter_nextattdatum(tuple1, tupDes); + datum2 = nbts_attiter_nextattdatum(tuple2, tupDes); + + if (nbts_attiter_attnum == 1) + { + compare = ApplySortAbbrevFullComparator(datum1, nbts_attiter_curattisnull(tuple1), + datum2, nbts_attiter_curattisnull(tuple2), + sortKey); + } + else + { + compare = ApplySortComparator(datum1, nbts_attiter_curattisnull(tuple1), + datum2, nbts_attiter_curattisnull(tuple2), + sortKey); + } + + if (compare != 0) + return compare; + + if (nbts_attiter_curattisnull(tuple1)) + equal_hasnull = true; + + sortKey++; + } + + /* + * If btree has asked us to enforce uniqueness, complain if two equal + * tuples are detected (unless there was at least one NULL field and NULLS + * NOT DISTINCT was not set). + * + * It is sufficient to make the test here, because if two tuples are equal + * they *must* get compared at some stage of the sort --- otherwise the + * sort algorithm wouldn't have checked whether one must appear before the + * other. + */ + if (state->enforceUnique && !(!state->uniqueNullsNotDistinct && equal_hasnull)) + { + Datum values[INDEX_MAX_KEYS]; + bool isnull[INDEX_MAX_KEYS]; + char *key_desc; + + /* + * Some rather brain-dead implementations of qsort (such as the one in + * QNX 4) will sometimes call the comparison routine to compare a + * value to itself, but we always use our own implementation, which + * does not. + */ + Assert(tuple1 != tuple2); + + index_deform_tuple(tuple1, tupDes, values, isnull); + + key_desc = BuildIndexValueDescription(state->indexRel, values, isnull); + + ereport(ERROR, + (errcode(ERRCODE_UNIQUE_VIOLATION), + errmsg("could not create unique index \"%s\"", + RelationGetRelationName(state->indexRel)), + key_desc ? errdetail("Key %s is duplicated.", key_desc) : + errdetail("Duplicate keys exist."), + errtableconstraint(state->heapRel, + RelationGetRelationName(state->indexRel)))); + } + + /* + * If key values are equal, we sort on ItemPointer. This is required for + * btree indexes, since heap TID is treated as an implicit last key + * attribute in order to ensure that all keys in the index are physically + * unique. + */ + { + BlockNumber blk1 = ItemPointerGetBlockNumber(&tuple1->t_tid); + BlockNumber blk2 = ItemPointerGetBlockNumber(&tuple2->t_tid); + + if (blk1 != blk2) + return (blk1 < blk2) ? -1 : 1; + } + { + OffsetNumber pos1 = ItemPointerGetOffsetNumber(&tuple1->t_tid); + OffsetNumber pos2 = ItemPointerGetOffsetNumber(&tuple2->t_tid); + + if (pos1 != pos2) + return (pos1 < pos2) ? -1 : 1; + } + + /* ItemPointer values should never be equal */ + Assert(false); + + return 0; +} + +#endif diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 92894e4ea7..11116b47ca 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1170,6 +1170,24 @@ do { \ ) \ ) +#define NBT_SPECIALIZE_NAME(name, rel) \ +( \ + IndexRelationGetNumberOfKeyAttributes(rel) == 1 ? ( \ + NBTS_MAKE_NAME(name, NBTS_TYPE_SINGLE_COLUMN) \ + ) \ + : \ + ( \ + TupleDescAttr(RelationGetDescr(rel), \ + IndexRelationGetNumberOfKeyAttributes(rel) - 1)->attcacheoff > 0 ? ( \ + NBTS_MAKE_NAME(name, NBTS_TYPE_CACHED) \ + ) \ + : \ + ( \ + NBTS_MAKE_NAME(name, NBTS_TYPE_UNCACHED) \ + ) \ + ) \ +) + #else /* not defined NBTS_ENABLED */ #define nbt_opt_specialize(rel) -- 2.30.2