Minor performance improvement in transition to external sort
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.
Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).
--
Cheers,
Jeremy
Attachments:
dtext/plain; charset=UTF-8; name=dDownload
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 8b520c1..2ea7b2d 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -1211,7 +1211,8 @@ puttuple_common(Tuplesortstate *state, SortTuple *tuple)
(void) grow_memtuples(state);
Assert(state->memtupcount < state->memtupsize);
}
- state->memtuples[state->memtupcount++] = *tuple;
+ state->memtuples[state->memtupcount] = *tuple;
+ state->memtuples[state->memtupcount++].tupindex = 0;
/*
* Check if it's time to switch over to a bounded heapsort. We do
@@ -1884,23 +1885,47 @@ inittapes(Tuplesortstate *state)
state->tp_tapenum = (int *) palloc0(maxTapes * sizeof(int));
/*
- * Convert the unsorted contents of memtuples[] into a heap. Each tuple is
- * marked as belonging to run number zero.
- *
- * NOTE: we pass false for checkIndex since there's no point in comparing
- * indexes in this step, even though we do intend the indexes to be part
- * of the sort key...
+ * Convert the unsorted contents of memtuples[] into a heap. Each tuple was
+ * marked as belonging to run number zero on input; we don't compare that.
*/
ntuples = state->memtupcount;
- state->memtupcount = 0; /* make the heap empty */
- for (j = 0; j < ntuples; j++)
{
- /* Must copy source tuple to avoid possible overwrite */
- SortTuple stup = state->memtuples[j];
+ SortTuple * m = state->memtuples;
- tuplesort_heap_insert(state, &stup, 0, false);
+ for (j = (ntuples-2)/2; /* comb the array from the last heap parent */
+ j >= 0; /* to the start */
+ j--)
+ {
+ int root = j;
+ int child, swap = 0;
+ SortTuple ptup = m[root];
+
+ while ((child = root*2 + 1) <= ntuples-1)
+ { /* root has at least one child. Check left-child */
+ if (COMPARETUP(state, &ptup, &m[child]) < 0)
+ { /* [root] < [lchild] */
+ if ( ++child <= ntuples-1 /* check right-child */
+ && COMPARETUP(state, &ptup, &m[child]) > 0)
+ swap = child;
+ else
+ break; /* [root] smallest */
+ }
+ else
+ {
+ swap = child;
+ if ( ++child <= ntuples-1 /* check right-child */
+ && COMPARETUP(state, &m[swap], &m[child]) > 0)
+ swap = child;
+ }
+ /* [swap] is smallest of three; move into the parent slot */
+ m[root] = m[swap];
+ root = swap; /* and repeat with the child subtree */
+ }
+ if (swap)
+ m[swap] = ptup;
+ /* This and all heap nodes after are now well-positioned */
+ }
}
- Assert(state->memtupcount == ntuples);
state->currentRun = 0;
On Wed, Feb 5, 2014 at 7:22 AM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).
It looks interesting but it is too late to have that in 9.4... You
should definitely add this patch to the next commit fest so as it is
not lost in the void:
https://commitfest.postgresql.org/action/commitfest_view?id=22
Regards,
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Feb 4, 2014 at 5:22 PM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).
I think Wikipedia is right. Inserting an a tuple into a heap is O(lg
n); doing that n times is therefore O(n lg n). Your patch likewise
implements an outer loop which runs O(n) times and an inner loop which
runs at most O(lg n) times, so a naive analysis of that algorithm also
comes out to O(n lg n). Wikipedia's article on
http://en.wikipedia.org/wiki/Heapsort explains why a tighter bound is
possible for the siftdown case.
I think I tested something like this at some point and concluded that
it didn't really help much, because building the initial heap was a
pretty small part of the runtime of a large sort. It may still be
worth doing, though.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05/02/14 21:56, Robert Haas wrote:
On Tue, Feb 4, 2014 at 5:22 PM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).I think Wikipedia is right. Inserting an a tuple into a heap is O(lg
n); doing that n times is therefore O(n lg n). Your patch likewise
implements an outer loop which runs O(n) times and an inner loop which
runs at most O(lg n) times, so a naive analysis of that algorithm also
comes out to O(n lg n).
The patch implements a siftdown. Measurements attached.
--
Cheers,
Jeremy
Attachments:
On Thu, Feb 6, 2014 at 4:22 AM, Jeremy Harris <jgh@wizmail.org> wrote:
On 05/02/14 21:56, Robert Haas wrote:
On Tue, Feb 4, 2014 at 5:22 PM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).I think Wikipedia is right. Inserting an a tuple into a heap is O(lg
n); doing that n times is therefore O(n lg n). Your patch likewise
implements an outer loop which runs O(n) times and an inner loop which
runs at most O(lg n) times, so a naive analysis of that algorithm also
comes out to O(n lg n).The patch implements a siftdown. Measurements attached.
Uh, this spreadsheet is useless. You haven't explained how these
numbers were generated, or what the columns in the spreadsheet
actually mean. Ideally, you should include enough information that
someone else can try to reproduce your results.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Feb 4, 2014 at 2:22 PM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.
Thanks for working on this. Several times I've wondered why we didn't use
siftdown for heapifying, as it is a well known optimization. How big of
sets were you sorting in each case? Was it always just slightly bigger
than would fit in work_mem? Did you try sorting already-sorted, reverse
sorted, or pipe-organ shaped data sets? We will also need to test it on
strings. I usually use md5(random()::text) to generate strings for such
purposes, at least for a first pass.
The name of the attached patch is a bit unfortunate. And I'm not sure what
you are doing with tupindex, the change there doesn't seem to be necessary
to the rest of your work, so some explanation on that would be helpful.
The project coding style usually has comments in blocks before loops on
which they comment, rather than interspersed throughout the clauses of the
"for" statement.
Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).
Siftdown is linear in all cases. Siftup may be linear in the typical case,
but is n log n in the worst case.
Another optimization that is possible is to do only one comparison at each
level (between the two children) all the way down the heap, and then
compare the parent to the chain of smallest children in reverse order.
This can substantially reduce the number of comparisons on average,
because most tuples sift most of the way down the heap (because most of the
tuples are at the bottom of the heap). Robert submitted a patch to do this
in the main siftdown routine (which for some reason is
named tuplesort_heap_siftup, rather than siftdown), and I found it a
substantial improvement but it never got pushed forward. I think Robert
was concerned it might make some obscure cases worse rather than better,
and no one put it through enough serious testing to overcome that concern.
(Also, I think you should make your siftdown code look more like the
existing siftdown code.)
Cheers,
Jeff
On 06/02/14 18:21, Jeff Janes wrote:
How big of
sets were you sorting in each case?
Big enough to go external. The timings and compare-counts given are
purely for the heapify stage not the total for the sort, so are
constrained by the work_mem not by the sort size per se.
I'm limited to working on a small machine, so the work_mem value
of 1e+6 is approaching my top limit of sort-time pain unless I rip the
heapify out into a test harness. What range of work_mem is it useful
to test over?
Was it always just slightly bigger
than would fit in work_mem?
I didn't check.
Did you try sorting already-sorted, reverse
sorted, or pipe-organ shaped data sets?
Not yet, but I can. What variety of pipe-organ shape is of
interest (I can think of several that might be called that)?
We will also need to test it on
strings. I usually use md5(random()::text) to generate strings for such
purposes, at least for a first pass.
OK.
The name of the attached patch is a bit unfortunate.
Is there a project standard for this?
And I'm not sure what
you are doing with tupindex, the change there doesn't seem to be necessary
to the rest of your work, so some explanation on that would be helpful.
I'll add commentary.
The project coding style usually has comments in blocks before loops on
which they comment, rather than interspersed throughout the clauses of the
"for" statement.
I'll adjust that.
Another optimization that is possible is to do only one comparison at each
level (between the two children) all the way down the heap, and then
compare the parent to the chain of smallest children in reverse order.
This can substantially reduce the number of comparisons on average,
because most tuples sift most of the way down the heap (because most of the
tuples are at the bottom of the heap).
Sounds interesting; I'll see if I can get that going.
(Also, I think you should make your siftdown code look more like the
existing siftdown code.)
A function called by the outer loop? Can do; the existing does that
because the function is called from multiple places but this will only
be used the once.
Thanks for the review.
--
Jeremy
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/02/14 14:22, Robert Haas wrote:
On Thu, Feb 6, 2014 at 4:22 AM, Jeremy Harris <jgh@wizmail.org> wrote:
On 05/02/14 21:56, Robert Haas wrote:
On Tue, Feb 4, 2014 at 5:22 PM, Jeremy Harris <jgh@wizmail.org> wrote:
The attached patch replaces the existing siftup method for heapify with
a siftdown method. Tested with random integers it does 18% fewer
compares and takes 10% less time for the heapify, over the work_mem
range 1024 to 1048576.Both algorithms appear to be O(n) (contradicting Wikipedia's claim
that a siftup heapify is O(n log n)).I think Wikipedia is right. Inserting an a tuple into a heap is O(lg
n); doing that n times is therefore O(n lg n). Your patch likewise
implements an outer loop which runs O(n) times and an inner loop which
runs at most O(lg n) times, so a naive analysis of that algorithm also
comes out to O(n lg n).The patch implements a siftdown. Measurements attached.
Uh, this spreadsheet is useless. You haven't explained how these
numbers were generated, or what the columns in the spreadsheet
actually mean. Ideally, you should include enough information that
someone else can try to reproduce your results.
I'm sorry I wasn't clear.
Test code was groups of the form:
set work_mem to 1024;
CREATE TABLE jgh (i integer);
INSERT INTO jgh (i) SELECT 20000 * random() FROM generate_series(1, 20000);
VACUUM ANALYZE jgh;
explain analyze SELECT * FROM jgh ORDER BY i;
set enable_siftdown_heapify to off;
explain analyze SELECT * FROM jgh ORDER BY i;
set enable_siftdown_heapify to on;
DROP TABLE jgh;
.. for the tabulated work_mem, and scaled values for the INSERT.
Compares were counted for the heapify stage (only), and timings
taken using pg_rusage_init() before and pg_rusage_show() after
it. Times are in seconds.
Baseline value for compares and time used 858ec11858.
Sift-down compares and time are for the patch.
"Baseline K cmps" is compares divided by work_mem (which should
be a scaled version of compares-per-tuple being heapified) pre-patch.
"Siftdown K cmps" is likewise with the patch.
"K ratio cmps" is the ratio (patched compares)/(unpatched compares).
Repeat for time measurements.
--
Cheers,
Jeremy
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/02/14 22:12, Jeremy Harris wrote:
Did you try sorting already-sorted, reverse
sorted, or pipe-organ shaped data sets?
Summary (low numbers better):
Random ints: 83% compares, level on time.
Sorted ints: level compares, 70% time.
Reverse-sorted ints: 10% compares, 15% time (!)
Constant ints: 200% compares, 360% time (ouch, and not O(n))
Pipe-organ ints: 80% compares, 107% time
Random text: 83% compares, 106% time
--
Cheers,
Jeremy
Attachments:
On Fri, Feb 7, 2014 at 4:28 PM, Jeremy Harris <jgh@wizmail.org> wrote:
On 06/02/14 22:12, Jeremy Harris wrote:
Did you try sorting already-sorted, reverse
sorted, or pipe-organ shaped data sets?Summary (low numbers better):
Random ints: 83% compares, level on time.
Sorted ints: level compares, 70% time.
Reverse-sorted ints: 10% compares, 15% time (!)
Constant ints: 200% compares, 360% time (ouch, and not O(n))
Pipe-organ ints: 80% compares, 107% time
Random text: 83% compares, 106% time
This is kind of what I expected to happen. The problem is that it's
hard to make some cases better without making other cases worse. I
suspect (hope?) there's some simple fix for the constant-int case.
But the last two cases are trickier. It seems intuitively that
reducing comparisons ought to reduce runtime, but if I'm reading the
results, the runtime actually went up even though the number of
comparisons went down. This is hard to account for, but we probably
need to at least understand why that's happening. I feel like this
algorithm ought to be a win, but these data don't provide a compelling
case for change.
I wonder if it would be worth trying this test with text data as well.
Text comparisons are much more expensive than integer comparisons, so
the effect of saving comparisons ought to be more visible there.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 06/02/14 18:21, Jeff Janes wrote:
Did you try sorting already-sorted, reverse
sorted, or pipe-organ shaped data sets? We will also need to test it on
strings. I usually use md5(random()::text) to generate strings for such
purposes, at least for a first pass.
Attached is version 2 of the patch, which fixes the performance on
constant-input.
Summary (low numbers better, vs 858ec11):
Random ints: 83% compares, level on time.
Sorted ints: level compares, 70% time.
Reverse-sorted ints: 10% compares, 15% time (!)
Constant ints: level compares, 75% time
Pipe-organ ints: 80% compares, 107% time
Random text: 83% compares, 106% time
Another optimization that is possible is to do only one comparison at each
level (between the two children) all the way down the heap, and then
compare the parent to the chain of smallest children in reverse order.
This can substantially reduce the number of comparisons on average,
because most tuples sift most of the way down the heap (because most of the
tuples are at the bottom of the heap). Robert submitted a patch to do this
in the main siftdown routine (which for some reason is
named tuplesort_heap_siftup, rather than siftdown), and I found it a
substantial improvement but it never got pushed forward. I think Robert
was concerned it might make some obscure cases worse rather than better,
and no one put it through enough serious testing to overcome that concern.
I've done an implementation of this (also attached: siftdown_bounce).
Summary:
Random ints: 72% compares, 104% time.
Sorted ints: 200% compares, 270% time. (ouch)
Reverse-sorted ints: 7% compares, 12% time
Constant ints: 150% compares, 275% time
Pipe-organ ints: 93% compares, 115% time
Random text: 72% compares, 91% time
- I don't like the performance on already-sorted input. Thinking
into this, a sorted array is already a well-formed heap so optimal
behaviour is a single compare per item (lacking the global knowledge).
This "bounce" method will always do a full walk down the tree and
back, hence the poor behaviour. Unless the planner can tell
the sort when sorted input is possible I don't think we dare use
this despite the benefit on random input.
The tests were run with an instrumented variant of each patch, counting
compares and time for just the heapify portion of the inittapes()
routine. Test script (jgh.sql and results spreadsheet) also attached.
--
Cheers,
Jeremy
Attachments:
heapify_siftdown_v2.patchtext/x-patch; name=heapify_siftdown_v2.patchDownload
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 8b520c1..f687baa 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -464,6 +464,7 @@ static void sort_bounded_heap(Tuplesortstate *state);
static void tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple,
int tupleindex, bool checkIndex);
static void tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex);
+static void tuplesort_heap_siftdown(Tuplesortstate *state, int start);
static unsigned int getlen(Tuplesortstate *state, int tapenum, bool eofOK);
static void markrunend(Tuplesortstate *state, int tapenum);
static int comparetup_heap(const SortTuple *a, const SortTuple *b,
@@ -1205,13 +1206,16 @@ puttuple_common(Tuplesortstate *state, SortTuple *tuple)
* still one free slot remaining --- if we fail, there'll still be
* room to store the incoming tuple, and then we'll switch to
* tape-based operation.
+ * Set the tape-number to zero here as it makes the code cleaner
+ * for converting from internal to external sort.
*/
if (state->memtupcount >= state->memtupsize - 1)
{
(void) grow_memtuples(state);
Assert(state->memtupcount < state->memtupsize);
}
- state->memtuples[state->memtupcount++] = *tuple;
+ state->memtuples[state->memtupcount] = *tuple;
+ state->memtuples[state->memtupcount++].tupindex = 0;
/*
* Check if it's time to switch over to a bounded heapsort. We do
@@ -1826,7 +1830,6 @@ static void
inittapes(Tuplesortstate *state)
{
int maxTapes,
- ntuples,
j;
int64 tapeSpace;
@@ -1884,23 +1887,15 @@ inittapes(Tuplesortstate *state)
state->tp_tapenum = (int *) palloc0(maxTapes * sizeof(int));
/*
- * Convert the unsorted contents of memtuples[] into a heap. Each tuple is
- * marked as belonging to run number zero.
- *
- * NOTE: we pass false for checkIndex since there's no point in comparing
- * indexes in this step, even though we do intend the indexes to be part
- * of the sort key...
+ * Convert the unsorted contents of memtuples[] into a heap. Each tuple was
+ * marked as belonging to run number zero on input; we don't compare that.
+ * We comb the array starting at the last heap parent and working back to the
+ * start; for each such item we sift it down to its proper heap position.
+ * After each iteration the start slot and all heap slots below make a
+ * well-formed heap.
*/
- ntuples = state->memtupcount;
- state->memtupcount = 0; /* make the heap empty */
- for (j = 0; j < ntuples; j++)
- {
- /* Must copy source tuple to avoid possible overwrite */
- SortTuple stup = state->memtuples[j];
-
- tuplesort_heap_insert(state, &stup, 0, false);
- }
- Assert(state->memtupcount == ntuples);
+ for (j = (state->memtupcount-2)/2; j >= 0; j--)
+ tuplesort_heap_siftdown(state, j);
state->currentRun = 0;
@@ -2749,6 +2744,51 @@ tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex)
memtuples[i] = *tuple;
}
+/*
+ * The tuple at state->memtuples[start] is being added in the heap,
+ * which is well-formed for all larger indices. Return with the
+ * heap well-formed for this and all larger indices.
+ *
+ * Working down from the start point: find the smallest tuple
+ * of it and its children. If the current is the smallest, we're
+ * done. Else, move the smallest into the current slot and repeat
+ * with the subtree of the smallest. When the current becomes
+ * smallest place it in the vacated slot.
+ */
+static void
+tuplesort_heap_siftdown(Tuplesortstate *state, int start)
+{
+ SortTuple * m = state->memtuples;
+ int ntuples = state->memtupcount;
+ int root = start;
+ int child, swap = 0;
+ SortTuple ptup = m[root];
+
+ while ((child = root*2 + 1) <= ntuples-1)
+ {
+ if (COMPARETUP(state, &ptup, &m[child]) <= 0)
+ {
+ if ( ++child <= ntuples-1
+ && COMPARETUP(state, &ptup, &m[child]) > 0)
+ swap = child;
+ else
+ break;
+ }
+ else
+ {
+ swap = child;
+ if ( ++child <= ntuples-1
+ && COMPARETUP(state, &m[swap], &m[child]) > 0)
+ swap = child;
+ }
+
+ m[root] = m[swap];
+ root = swap;
+ }
+ if (swap)
+ m[swap] = ptup;
+}
+
/*
* Tape interface routines
siftdown_bounce.patchtext/x-patch; name=siftdown_bounce.patchDownload
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 8b520c1..8de3dbd 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -464,6 +464,7 @@ static void sort_bounded_heap(Tuplesortstate *state);
static void tuplesort_heap_insert(Tuplesortstate *state, SortTuple *tuple,
int tupleindex, bool checkIndex);
static void tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex);
+static void tuplesort_heap_siftdown(Tuplesortstate *state, int start);
static unsigned int getlen(Tuplesortstate *state, int tapenum, bool eofOK);
static void markrunend(Tuplesortstate *state, int tapenum);
static int comparetup_heap(const SortTuple *a, const SortTuple *b,
@@ -1205,13 +1206,16 @@ puttuple_common(Tuplesortstate *state, SortTuple *tuple)
* still one free slot remaining --- if we fail, there'll still be
* room to store the incoming tuple, and then we'll switch to
* tape-based operation.
+ * Set the tape-number to zero here as it makes the code cleaner
+ * for converting from internal to external sort.
*/
if (state->memtupcount >= state->memtupsize - 1)
{
(void) grow_memtuples(state);
Assert(state->memtupcount < state->memtupsize);
}
- state->memtuples[state->memtupcount++] = *tuple;
+ state->memtuples[state->memtupcount] = *tuple;
+ state->memtuples[state->memtupcount++].tupindex = 0;
/*
* Check if it's time to switch over to a bounded heapsort. We do
@@ -1826,7 +1830,6 @@ static void
inittapes(Tuplesortstate *state)
{
int maxTapes,
- ntuples,
j;
int64 tapeSpace;
@@ -1884,23 +1887,15 @@ inittapes(Tuplesortstate *state)
state->tp_tapenum = (int *) palloc0(maxTapes * sizeof(int));
/*
- * Convert the unsorted contents of memtuples[] into a heap. Each tuple is
- * marked as belonging to run number zero.
- *
- * NOTE: we pass false for checkIndex since there's no point in comparing
- * indexes in this step, even though we do intend the indexes to be part
- * of the sort key...
+ * Convert the unsorted contents of memtuples[] into a heap. Each tuple was
+ * marked as belonging to run number zero on input; we don't compare that.
+ * We comb the array starting at the last heap parent and working back to the
+ * start; for each such item we sift it down to its proper heap position.
+ * After each iteration the start slot and all heap slots below make a
+ * well-formed heap.
*/
- ntuples = state->memtupcount;
- state->memtupcount = 0; /* make the heap empty */
- for (j = 0; j < ntuples; j++)
- {
- /* Must copy source tuple to avoid possible overwrite */
- SortTuple stup = state->memtuples[j];
-
- tuplesort_heap_insert(state, &stup, 0, false);
- }
- Assert(state->memtupcount == ntuples);
+ for (j = (state->memtupcount-2)/2; j >= 0; j--)
+ tuplesort_heap_siftdown(state, j);
state->currentRun = 0;
@@ -2749,6 +2744,55 @@ tuplesort_heap_siftup(Tuplesortstate *state, bool checkIndex)
memtuples[i] = *tuple;
}
+/*
+ * The tuple at state->memtuples[start] is being added in the heap,
+ * which is well-formed for all larger indices. Return with the
+ * heap well-formed for this and all larger indices.
+ *
+ * Working down from the start point: follow the smallest-child path
+ * down to the leaf, then back up until the new item is larger. Insert
+ * here, promoting all children one layer as far as the start slot.
+ */
+static inline void
+tuplesort_heap_siftdown(Tuplesortstate *state, int start)
+{
+ SortTuple * m = state->memtuples;
+ int ntup = state->memtupcount;
+ int par = start, child;
+ SortTuple tup, tup2;
+
+ /* Work down the smallest-child path to the leaf */
+ while ((child = par*2 + 1) < ntup)
+ par = child+1 < ntup && COMPARETUP(state, &m[child], &m[child+1]) > 0
+ ? child+1 : child;
+ child = par;
+
+ /* Work up until new item is larger or equal */
+ /*XXX Do we want early or late stop on equal?
+ Early is fewer compares but more data-moves.
+ Late is fewer data-moves but more compares.
+ Assume compares are expensive, so early is best.
+ */
+ while (child > start && COMPARETUP(state, &m[start], &m[child]) < 0)
+ child = (child-1)/2;
+
+ /* Insert here */
+ if (child != start)
+ {
+ tup = m[child];
+ m[child] = m[start];
+
+ while ((child = (child-1)/2) > start)
+ {
+ tup2 = m[child];
+ m[child] = tup;
+ tup = tup2;
+ }
+
+ m[start] = tup;
+ }
+}
+
/*
* Tape interface routines
siftdown_performance.odsapplication/vnd.oasis.opendocument.spreadsheet; name=siftdown_performance.odsDownload
PK ]{ID�l9�. . mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK ]{IDBZ�SK% K% Thumbnails/thumbnail.png�PNG
IHDR � c�N* %IDATx����VU������P;"��G���$���P��2S��L�d+�CHsQpYF&p������!���?�hTLBbL
T��ZT�b��g|��9��{����}w9�?��}�{�y�}x��~x~����_��wu��z��3E)����{����3E)���e��m������z���Gg�R�-����;��s��5�������LQj�����LQj��L\pA��_����������S������e����c�V�Z�h��#~���>���
����������5k�������q�������.y�E�_is������k�����k�����/������-[F�~�a��.0���/������O������n�z��i:���}������=��#���g����S�L�2}�t�sSS��i��x��.�g��->���L�4i��9���'���'Onoo����}�Y���H7�������^�r�J� ���K.������?��������g:��i���;�����c3�[��Vss��$c��C=4r����w������/?y���+h���] Z��0z�hr&r���z��'O2�#�����3����c#g�=���v�jkk�����
/]��������m�6:��Ar�3f$��~#]�|�n9�w���~��emmm�+���O^y�����;s�LMM�?����C��q�~�����C���}�#���]v�����:z������v����it�a��_��Wc���=�:u�.���6l��������{+Yd �����/��:j�(�@�X|;�sM��Yg����.��rr���g���+t�><[ZZ�����?��n������+^��7�����[nY�z��F��>/�t��tL�OO<�})l�����O�8q������u�]'����~���}�����g��&����|���O_p��J_�I��P�m:�L����?��:DN�P����>����?�wHz�D����~�3���;���s�����K�.���c�@#�.$>�u�
�?}>��G�
��y�|��VT�"��R���L3�0��o�������m���m�~g2,��G�m�fqN�����/�j��W_}�����D�MMM��'�"`^J��]�[��]lcR ��,������!����Y�f��m��Y(�n�����n�g��
����J2��mL*��x�u9'CH�rZ�"�L����9s
n �������������B6662�S��!C����d�����K/-��q���������6&��L^��2�\�~�����.]����F��gfW��T�3��<�Z����v�����LQj�����LQj�����LQj�����LQj�����LQj�~g2q�555�f�z��G�G���y���~gJ:�sj���W\q9�IT�0a�/��I��k�y����������1�[���$U�3]��9s�$)�o�������'�b�����M�1�T��
GI:�L���D�.[�l������������G�1 �I��
Gyu��*�6��9���o;v,f����k��c������l�_��5g\�~��"V��ysm9�8��Eu�����z��3E�):S��������&�@6?_~����|���=���I�������S�Ly��g��/���Hy ���u�f��=~�xN%�(4�:C3�K�03���o<��S��z�����c���m�{=��i��d�9^�t����~�aZ�����{��O|������SY��L�������7����n��m���yBC�3o��6������������]}����1��������%~�������_z�%:���k����hy:0��
��4������o���l��������z�����������Xg���%�e���^K�<�@��4>�lt�g����O~�����>�):�U|���C�������H�)ZZd~���u��t�.����|�+�%����}�;������b�������W?���_y���c�.Z�H:���m�{=����v1�#;w�\�A�y����3��'����i4��'�s>������t|D�=���{�������/�]
�L��/�0�C�7��O?���>�m��e��^Oeg�����kJy�����J��e������]������3!��<�������*x>�lt�gDQj�����LQjRv&�^�G�9��������|���k
<�����Q�~���$ %�i��.�&��T��;v���Z��_��W�[��L�o��
/��=[w�S�N�8q�_�~t��P�8>�T�^�p<FP���[-�F�V*;�D/��>}���_>k���k�s|�C��z� �`��\� �0�<)s��K�,��Z����Tv&�^�����{�nzi�������/|�c��%�� �`��\� �0�<)s���V3*�������Tv&�^����0f��[n���ZG�������� � s��i<>�T�^�p<FP�������������� Q�xy?$=�!w�D ����Rza�y����l�{�3*�F�zF4���LQj�w&�:�$�����k-ZD�"F,����Y��R2*#�A���El�`7dx��p�uJ����8�:�$�����/}�K555#��tq�B�f0�~����������P����!�A&q�
7,Y���7�x���U �4��d!.J X�:��a�2<dN8�C.�Xzprdk����g�z��) ���<x~k�Y�,�lS�0�
2'��!�l,=8��Hf_�����?�����/`������?q�������H�_�����u(������\P�����_-`��x��a�B�f0���aqV��9@��� ����Q6�����)JM������L p1L;t����%��]r�%���-����a 1#�C��Yk�H�J��8i�� %�j���L p1Lkmme�&fw��_�����a����@bF��P18����|�'�Uv �Q���I�}c�f���;�:+����W��pc���F<�0op��V��D5Rv&7`��a��� ����V^1#���g�Z6J�q�Ze J�H��0`
����L�
X����5r.�K p,$fz�3k-���~op���a#�eeT'������4����y��'����AbF����u�jjj2 ��� ����@���3E�):S���I��C��0�x}>3����u���T�l3=)�8�@���+��BB �bs6�5t��3I�w�f�Z��`}>���O���N%�6#��2��S��BB ����F��. U���"