0001-Specialize-tuplesort-for-keys-without-tiebreaker.patch
text/x-patch
Filename: 0001-Specialize-tuplesort-for-keys-without-tiebreaker.patch
Type: text/x-patch
Part: 0
Message:
Re: A qsort template
Patch
Format: format-patch
Series: patch 0001
Subject: Specialize tuplesort for keys without tiebreaker.
| File | + | − |
|---|---|---|
| src/backend/utils/sort/tuplesort.c | 89 | 21 |
From 3ff95aedb1065393a0ae1496cedb463bc998a329 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Wed, 6 Apr 2022 11:38:31 +1200
Subject: [PATCH] Specialize tuplesort for keys without tiebreaker.
Commit 69749243 noted a future opportunity to avoid calling the
comparator function in the case of single column non-abbreviated sort,
where datum1 alone is enough to decide the order.
David Rowley reported a performance regression in single-column integer
sorts with a lot of duplicates, where previously the qsort_ssup()
specialization was reached, due to extra calls to the comparator. It
seems like a good idea to fix that, so let's complete that TODO item
now.
XXX WIP, is the if-then-else getting too ugly, maybe switch to a table of
functions to search?
XXX needs testing, experiments
---
src/backend/utils/sort/tuplesort.c | 110 +++++++++++++++++++++++------
1 file changed, 89 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 571fb95532..dc16e6c242 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -312,6 +312,14 @@ struct Tuplesortstate
*/
bool haveDatum1;
+ /*
+ * Whether the comparator function must always be called for tuples with
+ * equal datum1. This should be set to true if the sorting has extra
+ * 'invisible' sort order beyond the regular keys, to disable optimizations
+ * that would otherwise skip the function call.
+ */
+ bool alwaysTiebreak;
+
/*
* This array holds the tuples now in sort memory. If we are in state
* INITIAL, the tuples are in no particular order; if we are in state
@@ -682,11 +690,6 @@ static void tuplesort_updatemax(Tuplesortstate *state);
*
* XXX: For now, these fall back to comparator functions that will compare the
* leading datum a second time.
- *
- * XXX: For now, there is no specialization for cases where datum1 is
- * authoritative and we don't even need to fall back to a callback at all (that
- * would be true for types like int4/int8/timestamp/date, but not true for
- * abbreviations of text or multi-key sorts. There could be! Is it worth it?
*/
/* Used if first key's comparator is ssup_datum_unsigned_compare */
@@ -740,10 +743,12 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
* qsort_ssup() is specialized for the case where the comparetup function
* reduces to ApplySortComparator(), that is single-key MinimalTuple sorts
* and Datum sorts. qsort_tuple_{unsigned,signed,int32} are specialized for
- * common comparison functions on pass-by-value leading datums.
+ * common comparison functions on pass-by-value leading datums. The _tiebreak
+ * variants are for when it is necessary to call the comparator function even
+ * if datum1 is equal.
*/
-#define ST_SORT qsort_tuple_unsigned
+#define ST_SORT qsort_tuple_unsigned_tiebreak
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_unsigned_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
@@ -752,7 +757,7 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
#define ST_DEFINE
#include "lib/sort_template.h"
-#define ST_SORT qsort_tuple_signed
+#define ST_SORT qsort_tuple_signed_tiebreak
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_signed_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
@@ -761,7 +766,7 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
#define ST_DEFINE
#include "lib/sort_template.h"
-#define ST_SORT qsort_tuple_int32
+#define ST_SORT qsort_tuple_int32_tiebreak
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE(a, b, state) qsort_tuple_int32_compare(a, b, state)
#define ST_COMPARE_ARG_TYPE Tuplesortstate
@@ -770,6 +775,33 @@ qsort_tuple_int32_compare(SortTuple *a, SortTuple *b, Tuplesortstate *state)
#define ST_DEFINE
#include "lib/sort_template.h"
+#define ST_SORT qsort_tuple_unsigned
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) ApplyUnsignedSortComparator(a->datum1, a->isnull1, b->datum1, b->isnull1, &state->sortKeys[0])
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
+#define ST_SORT qsort_tuple_signed
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) ApplySignedSortComparator(a->datum1, a->isnull1, b->datum1, b->isnull1, &state->sortKeys[0])
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
+#define ST_SORT qsort_tuple_int32
+#define ST_ELEMENT_TYPE SortTuple
+#define ST_COMPARE(a, b, state) ApplyInt32SortComparator(a->datum1, a->isnull1, b->datum1, b->isnull1, &state->sortKeys[0])
+#define ST_COMPARE_ARG_TYPE Tuplesortstate
+#define ST_CHECK_FOR_INTERRUPTS
+#define ST_SCOPE static
+#define ST_DEFINE
+#include "lib/sort_template.h"
+
#define ST_SORT qsort_tuple
#define ST_ELEMENT_TYPE SortTuple
#define ST_COMPARE_RUNTIME_POINTER
@@ -1215,6 +1247,12 @@ tuplesort_begin_index_btree(Relation heapRel,
state->abbrevNext = 10;
state->haveDatum1 = true;
+ /*
+ * Since we sort equal keys by ItemPointer, we can't skip the comparator
+ * call.
+ */
+ state->alwaysTiebreak = true;
+
state->heapRel = heapRel;
state->indexRel = indexRel;
state->enforceUnique = enforceUnique;
@@ -3632,28 +3670,58 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
*/
if (state->haveDatum1 && state->sortKeys)
{
+ bool tiebreak;
+
+ /*
+ * If there's only a single key, and the value in datum1 is not a
+ * lossy abbreviation, then we don't need to call a tie-breaking
+ * comparator function for equal datum1 values unless explicitly
+ * asked to do so (eg btrees).
+ */
+ tiebreak = state->nKeys > 1 ||
+ state->sortKeys[0].abbrev_converter ||
+ state->alwaysTiebreak;
+
if (state->sortKeys[0].comparator == ssup_datum_unsigned_cmp)
{
- elog(DEBUG1, "qsort_tuple_unsigned");
- qsort_tuple_unsigned(state->memtuples,
- state->memtupcount,
- state);
+ elog(DEBUG1, "qsort_tuple_unsigned%s",
+ tiebreak ? "_tiebreak" : "");
+ if (tiebreak)
+ qsort_tuple_unsigned_tiebreak(state->memtuples,
+ state->memtupcount,
+ state);
+ else
+ qsort_tuple_unsigned(state->memtuples,
+ state->memtupcount,
+ state);
return;
}
else if (state->sortKeys[0].comparator == ssup_datum_signed_cmp)
{
- elog(DEBUG1, "qsort_tuple_signed");
- qsort_tuple_signed(state->memtuples,
- state->memtupcount,
- state);
+ elog(DEBUG1, "qsort_tuple_signed%s",
+ tiebreak ? "_tiebreak" : "");
+ if (tiebreak)
+ qsort_tuple_signed_tiebreak(state->memtuples,
+ state->memtupcount,
+ state);
+ else
+ qsort_tuple_signed(state->memtuples,
+ state->memtupcount,
+ state);
return;
}
else if (state->sortKeys[0].comparator == ssup_datum_int32_cmp)
{
- elog(DEBUG1, "qsort_tuple_int32");
- qsort_tuple_int32(state->memtuples,
- state->memtupcount,
- state);
+ elog(DEBUG1, "qsort_tuple_int32%s",
+ tiebreak ? "_tiebreak" : "");
+ if (tiebreak)
+ qsort_tuple_int32_tiebreak(state->memtuples,
+ state->memtupcount,
+ state);
+ else
+ qsort_tuple_int32(state->memtuples,
+ state->memtupcount,
+ state);
return;
}
}
--
2.30.2