public inbox for [email protected]
help / color / mirror / Atom feedFrom: =?utf-8?B?Y2NhNTUwNw==?= <[email protected]>
To: =?utf-8?B?Sm9obiBOYXlsb3I=?= <[email protected]>
To: =?utf-8?B?U2FtaSBJbXNlaWg=?= <[email protected]>
Cc: =?utf-8?B?SGVpa2tpIExpbm5ha2FuZ2Fz?= <[email protected]>
Cc: =?utf-8?B?cGdzcWwtaGFja2Vycw==?= <[email protected]>
Subject: Re: Support loser tree for k-way merge
Date: Sat, 6 Dec 2025 00:11:39 +0800
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<CAA5RZ0u=WDa-v+8Y_yE4WeRS8VtmcmQGuWVjZFCqZcXx-hEP3w@mail.gmail.com>
<CANWCAZYKgc4xFbBXtePw_XY+a+Ao_-CxZ+Z8YWSrK2B1HqtdWg@mail.gmail.com>
<[email protected]>
Hi,
Can we support loser tree first and set the default value of enable_loser_tree to off?
Anyway, I attach 2 patches. (pass check-world)
v2-0001: support tuplesort_heap_build() which can build a valid heap in O(n), better
than currently O(n log n). This also make beginmerge() more clear and simple when
supporting loser tree.
v2-0002: support the loser tree.
--
Regards,
ChangAo Chen
Attachments:
[application/octet-stream] v2-0001-Support-tuplesort_heap_build-in-tuplesort.c.patch (5.2K, 2-v2-0001-Support-tuplesort_heap_build-in-tuplesort.c.patch)
download | inline diff:
From bef514a687bcdc6f13b84ded17d62ab3cf1ff848 Mon Sep 17 00:00:00 2001
From: ChangAo Chen <[email protected]>
Date: Fri, 5 Dec 2025 14:37:21 +0800
Subject: [PATCH v2 1/2] Support tuplesort_heap_build() in tuplesort.c.
Build a valid heap with n tuples by using tuplesort_heap_insert() is
O(n log n), we can use tuplesort_heap_build() to optimize it, which
is O(n).
---
src/backend/utils/sort/tuplesort.c | 103 +++++++++++++++--------------
1 file changed, 53 insertions(+), 50 deletions(-)
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 5d4411dc33f..e28b8cfa868 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -465,7 +465,7 @@ static void dumptuples(Tuplesortstate *state, bool alltuples);
static void make_bounded_heap(Tuplesortstate *state);
static void sort_bounded_heap(Tuplesortstate *state);
static void tuplesort_sort_memtuples(Tuplesortstate *state);
-static void tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple);
+static void tuplesort_heap_build(Tuplesortstate *state);
static void tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple);
static void tuplesort_heap_delete_top(Tuplesortstate *state);
static void reversedirection(Tuplesortstate *state);
@@ -2269,10 +2269,14 @@ beginmerge(Tuplesortstate *state)
if (mergereadnext(state, state->inputTapes[srcTapeIndex], &tup))
{
+ Assert(state->memtupcount < state->memtupsize);
+ CHECK_FOR_INTERRUPTS();
tup.srctape = srcTapeIndex;
- tuplesort_heap_insert(state, &tup);
+ state->memtuples[state->memtupcount++] = tup;
}
}
+
+ tuplesort_heap_build(state);
}
/*
@@ -2593,32 +2597,25 @@ make_bounded_heap(Tuplesortstate *state)
/* Reverse sort direction so largest entry will be at root */
reversedirection(state);
- state->memtupcount = 0; /* make the heap empty */
- for (i = 0; i < tupcount; i++)
+ /* Build a valid heap from the first "state->bound" tuples */
+ state->memtupcount = state->bound;
+ tuplesort_heap_build(state);
+
+ /* Process the remaining tuples */
+ for (i = state->bound; i < tupcount; i++)
{
- if (state->memtupcount < state->bound)
+ /*
+ * The heap is full. Replace the largest entry with the new
+ * tuple, or just discard it, if it's larger than anything already
+ * in the heap.
+ */
+ if (COMPARETUP(state, &state->memtuples[i], &state->memtuples[0]) <= 0)
{
- /* Insert next tuple into heap */
- /* Must copy source tuple to avoid possible overwrite */
- SortTuple stup = state->memtuples[i];
-
- tuplesort_heap_insert(state, &stup);
+ free_sort_tuple(state, &state->memtuples[i]);
+ CHECK_FOR_INTERRUPTS();
}
else
- {
- /*
- * The heap is full. Replace the largest entry with the new
- * tuple, or just discard it, if it's larger than anything already
- * in the heap.
- */
- if (COMPARETUP(state, &state->memtuples[i], &state->memtuples[0]) <= 0)
- {
- free_sort_tuple(state, &state->memtuples[i]);
- CHECK_FOR_INTERRUPTS();
- }
- else
- tuplesort_heap_replace_top(state, &state->memtuples[i]);
- }
+ tuplesort_heap_replace_top(state, &state->memtuples[i]);
}
Assert(state->memtupcount == state->bound);
@@ -2721,40 +2718,46 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
}
/*
- * Insert a new tuple into an empty or existing heap, maintaining the
- * heap invariant. Caller is responsible for ensuring there's room.
- *
- * Note: For some callers, tuple points to a memtuples[] entry above the
- * end of the heap. This is safe as long as it's not immediately adjacent
- * to the end of the heap (ie, in the [memtupcount] array entry) --- if it
- * is, it might get overwritten before being moved into the heap!
+ * Build a valid heap from memtuples[].
*/
static void
-tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple)
+tuplesort_heap_build(Tuplesortstate *state)
{
- SortTuple *memtuples;
- int j;
-
- memtuples = state->memtuples;
- Assert(state->memtupcount < state->memtupsize);
-
- CHECK_FOR_INTERRUPTS();
+ SortTuple *memtuples = state->memtuples;
+ SortTuple stup;
+ int hole;
+ unsigned int i, j, n;
/*
- * Sift-up the new entry, per Knuth 5.2.3 exercise 16. Note that Knuth is
- * using 1-based array indexes, not 0-based.
+ * state->memtupcount is "int", but we use "unsigned int" for i, j, n.
+ * This prevents overflow in the "2 * i + 1" calculation, since at the top
+ * of the loop we must have i < n <= INT_MAX <= UINT_MAX/2.
*/
- j = state->memtupcount++;
- while (j > 0)
+ n = state->memtupcount;
+ for (hole = (state->memtupcount - 2) / 2; hole >= 0; hole--)
{
- int i = (j - 1) >> 1;
-
- if (COMPARETUP(state, tuple, &memtuples[i]) >= 0)
- break;
- memtuples[j] = memtuples[i];
- j = i;
+ CHECK_FOR_INTERRUPTS();
+ /* i is where the "hole" is */
+ i = hole;
+ /* copy memtuples[i] because it will get overwritten below */
+ stup = memtuples[i];
+ /* sift down */
+ for (;;)
+ {
+ j = 2 * i + 1;
+
+ if (j >= n)
+ break;
+ if (j + 1 < n &&
+ COMPARETUP(state, &memtuples[j], &memtuples[j + 1]) > 0)
+ j++;
+ if (COMPARETUP(state, &stup, &memtuples[j]) <= 0)
+ break;
+ memtuples[i] = memtuples[j];
+ i = j;
+ }
+ memtuples[i] = stup;
}
- memtuples[j] = *tuple;
}
/*
--
2.52.0
[application/octet-stream] v2-0002-Support-loser-tree-for-k-way-merge.patch (16.2K, 3-v2-0002-Support-loser-tree-for-k-way-merge.patch)
download | inline diff:
From 9dbf07f28d257bd6aef8bacb38076f0da9fc8a45 Mon Sep 17 00:00:00 2001
From: ChangAo Chen <[email protected]>
Date: Fri, 5 Dec 2025 23:48:05 +0800
Subject: [PATCH v2 2/2] Support loser tree for k-way merge.
The loser tree usually has fewer comparisons than the heap during
k-way merge. But the heap maybe better if the cardinality of the
input data is very low. Add a GUC 'enable_loser_tree' to control
the use of loser tree, default to off.
---
doc/src/sgml/config.sgml | 14 ++
src/backend/utils/misc/guc_parameters.dat | 7 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/backend/utils/sort/tuplesort.c | 218 ++++++++++++++----
src/include/utils/guc.h | 1 +
src/test/regress/expected/sysviews.out | 3 +-
6 files changed, 202 insertions(+), 42 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 405c9689bd0..031591bb820 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5617,6 +5617,20 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
</listitem>
</varlistentry>
+ <varlistentry id="guc-enable-loser-tree" xreflabel="enable_loser_tree">
+ <term><varname>enable_loser_tree</varname> (<type>boolean</type>)
+ <indexterm>
+ <primary><varname>enable_loser_tree</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Enables or disables the external k-way merge's use of loser tree.
+ The default is <literal>off</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-enable-material" xreflabel="enable_material">
<term><varname>enable_material</varname> (<type>boolean</type>)
<indexterm>
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3b9d8349078..cbece911495 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -882,6 +882,13 @@
boot_val => 'true',
},
+{ name => 'enable_loser_tree', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+ short_desc => 'Enables loser tree for external k-way merge.',
+ flags => 'GUC_EXPLAIN',
+ variable => 'enable_loser_tree',
+ boot_val => 'false',
+},
+
{ name => 'enable_material', type => 'bool', context => 'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
short_desc => 'Enables the planner\'s use of materialization.',
flags => 'GUC_EXPLAIN',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..2ffba918a43 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -412,6 +412,7 @@
#enable_incremental_sort = on
#enable_indexscan = on
#enable_indexonlyscan = on
+#enable_loser_tree = off
#enable_material = on
#enable_memoize = on
#enable_mergejoin = on
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index e28b8cfa868..57c3d0266dc 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -42,16 +42,16 @@
* end of the input is reached, we dump out remaining tuples in memory into
* a final run, then merge the runs.
*
- * When merging runs, we use a heap containing just the frontmost tuple from
- * each source run; we repeatedly output the smallest tuple and replace it
- * with the next tuple from its source tape (if any). When the heap empties,
- * the merge is complete. The basic merge algorithm thus needs very little
- * memory --- only M tuples for an M-way merge, and M is constrained to a
- * small number. However, we can still make good use of our full workMem
- * allocation by pre-reading additional blocks from each source tape. Without
- * prereading, our access pattern to the temporary file would be very erratic;
- * on average we'd read one block from each of M source tapes during the same
- * time that we're writing M blocks to the output tape, so there is no
+ * When merging runs, we use a heap or loser tree containing just the frontmost
+ * tuple from each source run; we repeatedly output the smallest tuple and
+ * replace it with the next tuple from its source tape (if any). When the heap
+ * or loser tree empties, the merge is complete. The basic merge algorithm thus
+ * needs very little memory --- only M tuples for an M-way merge, and M is
+ * constrained to a small number. However, we can still make good use of our
+ * full workMem allocation by pre-reading additional blocks from each source tape.
+ * Without prereading, our access pattern to the temporary file would be very
+ * erratic; on average we'd read one block from each of M source tapes during
+ * the same time that we're writing M blocks to the output tape, so there is no
* sequentiality of access at all, defeating the read-ahead methods used by
* most Unix kernels. Worse, the output tape gets written into a very random
* sequence of blocks of the temp file, ensuring that things will be even
@@ -120,8 +120,12 @@
#define INITIAL_MEMTUPSIZE Max(1024, \
ALLOCSET_SEPARATE_THRESHOLD / sizeof(SortTuple) + 1)
+/* LOSER_TREE_EOF is always a loser in comparison */
+#define LOSER_TREE_EOF -1
+
/* GUC variables */
bool trace_sort = false;
+bool enable_loser_tree = false;
#ifdef DEBUG_BOUNDED_SORT
bool optimize_bounded_sort = true;
@@ -206,6 +210,8 @@ struct Tuplesortstate
* space */
TupSortStatus maxSpaceStatus; /* sort status when maxSpace was reached */
LogicalTapeSet *tapeset; /* logtape.c object for tapes in a temp file */
+ bool useLoserTree; /* use loser tree for k-way merge if true */
+ int16 *losers; /* array of losers, losers[0] is the winner */
/*
* This array holds the tuples now in sort memory. If we are in state
@@ -468,6 +474,8 @@ static void tuplesort_sort_memtuples(Tuplesortstate *state);
static void tuplesort_heap_build(Tuplesortstate *state);
static void tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple);
static void tuplesort_heap_delete_top(Tuplesortstate *state);
+static void tuplesort_loser_tree_build(Tuplesortstate *state);
+static void tuplesort_loser_tree_adjust(Tuplesortstate *state);
static void reversedirection(Tuplesortstate *state);
static unsigned int getlen(LogicalTape *tape, bool eofOK);
static void markrunend(LogicalTape *tape);
@@ -682,6 +690,10 @@ tuplesort_begin_common(int workMem, SortCoordinate coordinate, int sortopt)
state->base.tuples = true;
state->abbrevNext = 10;
+ /* Use loser tree for k-way merge if enable_loser_tree is true */
+ state->useLoserTree = enable_loser_tree;
+ state->losers = NULL;
+
/*
* workMem is forced to be at least 64KB, the current minimum valid value
* for the work_mem GUC. This is a defense against parallel sort callers
@@ -1652,11 +1664,12 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
*/
if (state->memtupcount > 0)
{
- int srcTapeIndex = state->memtuples[0].srctape;
+ int i = (state->useLoserTree ? state->losers[0] : 0);
+ int srcTapeIndex = state->memtuples[i].srctape;
LogicalTape *srcTape = state->inputTapes[srcTapeIndex];
SortTuple newtup;
- *stup = state->memtuples[0];
+ *stup = state->memtuples[i];
/*
* Remember the tuple we return, so that we can recycle its
@@ -1665,16 +1678,17 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
state->lastReturnedTuple = stup->tuple;
/*
- * Pull next tuple from tape, and replace the returned tuple
- * at top of the heap with it.
+ * Pull next tuple from tape, and adjust the heap or loser tree.
*/
if (!mergereadnext(state, srcTape, &newtup))
{
/*
* If no more data, we've reached end of run on this tape.
- * Remove the top node from the heap.
*/
- tuplesort_heap_delete_top(state);
+ if (state->useLoserTree)
+ state->memtuples[i].srctape = LOSER_TREE_EOF;
+ else
+ tuplesort_heap_delete_top(state);
state->nInputRuns--;
/*
@@ -1682,10 +1696,22 @@ tuplesort_gettuple_common(Tuplesortstate *state, bool forward,
* anyway, but better to release the memory early.
*/
LogicalTapeClose(srcTape);
- return true;
}
- newtup.srctape = srcTapeIndex;
- tuplesort_heap_replace_top(state, &newtup);
+ else
+ {
+ newtup.srctape = srcTapeIndex;
+ if (state->useLoserTree)
+ state->memtuples[i] = newtup;
+ else
+ tuplesort_heap_replace_top(state, &newtup);
+ }
+
+ if (state->useLoserTree)
+ {
+ tuplesort_loser_tree_adjust(state);
+ if (state->losers[0] == LOSER_TREE_EOF)
+ state->memtupcount = 0;
+ }
return true;
}
return false;
@@ -2066,8 +2092,8 @@ mergeruns(Tuplesortstate *state)
init_slab_allocator(state, 0);
/*
- * Allocate a new 'memtuples' array, for the heap. It will hold one tuple
- * from each input tape.
+ * Allocate a new 'memtuples' array. It will hold one tuple from each
+ * input tape.
*
* We could shrink this, too, between passes in a multi-pass merge, but we
* don't bother. (The initial input tapes are still in outputTapes. The
@@ -2078,6 +2104,14 @@ mergeruns(Tuplesortstate *state)
state->nOutputTapes * sizeof(SortTuple));
USEMEM(state, GetMemoryChunkSpace(state->memtuples));
+ /* Allocate a 'losers' array if we will use loser tree for merge */
+ if (state->useLoserTree && state->losers == NULL)
+ {
+ state->losers = (int16 *) MemoryContextAlloc(state->base.maincontext,
+ state->nOutputTapes * sizeof(int16));
+ USEMEM(state, GetMemoryChunkSpace(state->losers));
+ }
+
/*
* Use all the remaining memory we have available for tape buffers among
* all the input tapes. At the beginning of each merge pass, we will
@@ -2195,62 +2229,72 @@ mergeruns(Tuplesortstate *state)
static void
mergeonerun(Tuplesortstate *state)
{
+ int i;
int srcTapeIndex;
LogicalTape *srcTape;
/*
* Start the merge by loading one tuple from each active source tape into
- * the heap.
+ * the heap or loser tree.
*/
beginmerge(state);
Assert(state->slabAllocatorUsed);
/*
- * Execute merge by repeatedly extracting lowest tuple in heap, writing it
- * out, and replacing it with next tuple from same tape (if there is
- * another one).
+ * Execute merge by repeatedly extracting the tuple in the heap or loser
+ * tree, writing it out, pulling next tuple from same tape (if there is
+ * another one), and adjusting the heap or loser tree.
*/
while (state->memtupcount > 0)
{
SortTuple stup;
/* write the tuple to destTape */
- srcTapeIndex = state->memtuples[0].srctape;
+ i = (state->useLoserTree ? state->losers[0] : 0);
+ srcTapeIndex = state->memtuples[i].srctape;
srcTape = state->inputTapes[srcTapeIndex];
- WRITETUP(state, state->destTape, &state->memtuples[0]);
+ WRITETUP(state, state->destTape, &state->memtuples[i]);
/* recycle the slot of the tuple we just wrote out, for the next read */
- if (state->memtuples[0].tuple)
- RELEASE_SLAB_SLOT(state, state->memtuples[0].tuple);
+ if (state->memtuples[i].tuple)
+ RELEASE_SLAB_SLOT(state, state->memtuples[i].tuple);
- /*
- * pull next tuple from the tape, and replace the written-out tuple in
- * the heap with it.
- */
+ /* pull next tuple from the tape, and adjust the heap or loser tree */
if (mergereadnext(state, srcTape, &stup))
{
stup.srctape = srcTapeIndex;
- tuplesort_heap_replace_top(state, &stup);
+ if (state->useLoserTree)
+ state->memtuples[i] = stup;
+ else
+ tuplesort_heap_replace_top(state, &stup);
}
else
{
- tuplesort_heap_delete_top(state);
+ if (state->useLoserTree)
+ state->memtuples[i].srctape = LOSER_TREE_EOF;
+ else
+ tuplesort_heap_delete_top(state);
state->nInputRuns--;
}
+
+ if (state->useLoserTree)
+ {
+ tuplesort_loser_tree_adjust(state);
+ if (state->losers[0] == LOSER_TREE_EOF)
+ state->memtupcount = 0;
+ }
}
- /*
- * When the heap empties, we're done. Write an end-of-run marker on the
- * output tape.
- */
+ /* Done. Write an end-of-run marker on the output tape. */
markrunend(state->destTape);
}
/*
* beginmerge - initialize for a merge pass
*
- * Fill the merge heap with the first tuple from each input tape.
+ * Pull the first tuple from each input tape, and build a heap
+ * or loser tree for the merge pass.
*/
static void
beginmerge(Tuplesortstate *state)
@@ -2276,7 +2320,10 @@ beginmerge(Tuplesortstate *state)
}
}
- tuplesort_heap_build(state);
+ if (state->useLoserTree)
+ tuplesort_loser_tree_build(state);
+ else
+ tuplesort_heap_build(state);
}
/*
@@ -2826,6 +2873,95 @@ tuplesort_heap_replace_top(Tuplesortstate *state, SortTuple *tuple)
memtuples[i] = *tuple;
}
+/*
+ * Build a valid loser tree from memtuples[].
+ */
+static void
+tuplesort_loser_tree_build(Tuplesortstate *state)
+{
+ SortTuple *memtuples = state->memtuples;
+ int k = state->memtupcount;
+ int16 winners[MAXORDER];
+ int i;
+
+ Assert(state->losers != NULL);
+ Assert(k <= MAXORDER);
+
+ /* this can happen? */
+ if (unlikely(k <= 0))
+ return;
+
+ /* 1-way merge, losers[0] is always 0 */
+ if (k == 1)
+ {
+ state->losers[0] = 0;
+ return;
+ }
+
+ /* fill the losers array */
+ for (i = k - 1; i > 0; i--)
+ {
+ int l = i * 2;
+ int r = l + 1;
+ int ll = (l >= k ? l - k : winners[l]);
+ int rr = (r >= k ? r - k : winners[r]);
+
+ if (COMPARETUP(state, &memtuples[ll], &memtuples[rr]) < 0)
+ {
+ winners[i] = ll;
+ state->losers[i] = rr;
+ }
+ else
+ {
+ winners[i] = rr;
+ state->losers[i] = ll;
+ }
+ }
+ state->losers[0] = winners[1];
+}
+
+/*
+ * Adjust the loser tree to get the new winner. Caller must have already
+ * pulled the next tuple from the last winner's input tape, and placed it
+ * to memtuples[losers[0]].
+ */
+static void
+tuplesort_loser_tree_adjust(Tuplesortstate *state)
+{
+ SortTuple *memtuples = state->memtuples;
+ int k = state->memtupcount;
+ int winner = state->losers[0];
+ int i = (winner + k) / 2;
+
+ Assert(state->losers != NULL);
+ Assert(k > 0 && k <= MAXORDER);
+ Assert(winner >= 0 && winner < k);
+
+ /*
+ * Reach end of run on the tape, set winner to LOSER_TREE_EOF so that
+ * it's always a loser in comparison.
+ */
+ if (memtuples[winner].srctape == LOSER_TREE_EOF)
+ winner = LOSER_TREE_EOF;
+
+ for (; i > 0; i /= 2)
+ {
+ int loser = state->losers[i];
+
+ /* LOSER_TREE_EOF is always a loser */
+ if (loser == LOSER_TREE_EOF)
+ continue;
+
+ if (winner == LOSER_TREE_EOF ||
+ COMPARETUP(state, &memtuples[winner], &memtuples[loser]) > 0)
+ {
+ state->losers[i] = winner;
+ winner = loser;
+ }
+ }
+ state->losers[0] = winner;
+}
+
/*
* Function to reverse the sort direction from its current state
*
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da89..8c22eed716b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -324,6 +324,7 @@ extern PGDLLIMPORT int tcp_user_timeout;
extern PGDLLIMPORT char *role_string;
extern PGDLLIMPORT bool in_hot_standby_guc;
extern PGDLLIMPORT bool trace_sort;
+extern PGDLLIMPORT bool enable_loser_tree;
#ifdef DEBUG_BOUNDED_SORT
extern PGDLLIMPORT bool optimize_bounded_sort;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 0411db832f1..b492e9de6fd 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -159,6 +159,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_incremental_sort | on
enable_indexonlyscan | on
enable_indexscan | on
+ enable_loser_tree | off
enable_material | on
enable_memoize | on
enable_mergejoin | on
@@ -173,7 +174,7 @@ select name, setting from pg_settings where name like 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(25 rows)
+(26 rows)
-- There are always wait event descriptions for various types. InjectionPoint
-- may be present or absent, depending on history since last postmaster start.
--
2.52.0
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Support loser tree for k-way merge
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox