Commit d26559db authored by Tom Lane's avatar Tom Lane

Teach tuplesort.c about "top N" sorting, in which only the first N tuples

need be returned.  We keep a heap of the current best N tuples and sift-up
new tuples into it as we scan the input.  For M input tuples this means
only about M*log(N) comparisons instead of M*log(M), not to mention a lot
less workspace when N is small --- avoiding spill-to-disk for large M
is actually the most attractive thing about it.  Patch includes planner
and executor support for invoking this facility in ORDER BY ... LIMIT
queries.  Greg Stark, with some editorialization by moi.
parent 0fef38da
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.29 2007/01/05 22:19:28 momjian Exp $ * $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.30 2007/05/04 01:13:43 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -280,6 +280,37 @@ recompute_limits(LimitState *node) ...@@ -280,6 +280,37 @@ recompute_limits(LimitState *node)
/* Reset position to start-of-scan */ /* Reset position to start-of-scan */
node->position = 0; node->position = 0;
node->subSlot = NULL; node->subSlot = NULL;
/*
* If we have a COUNT, and our input is a Sort node, notify it that it can
* use bounded sort.
*
* This is a bit of a kluge, but we don't have any more-abstract way of
* communicating between the two nodes; and it doesn't seem worth trying
* to invent one without some more examples of special communication needs.
*
* Note: it is the responsibility of nodeSort.c to react properly to
* changes of these parameters. If we ever do redesign this, it'd be
* a good idea to integrate this signaling with the parameter-change
* mechanism.
*/
if (IsA(outerPlanState(node), SortState))
{
SortState *sortState = (SortState *) outerPlanState(node);
int64 tuples_needed = node->count + node->offset;
/* negative test checks for overflow */
if (node->noCount || tuples_needed < 0)
{
/* make sure flag gets reset if needed upon rescan */
sortState->bounded = false;
}
else
{
sortState->bounded = true;
sortState->bound = tuples_needed;
}
}
} }
/* ---------------------------------------------------------------- /* ----------------------------------------------------------------
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeSort.c,v 1.60 2007/01/09 02:14:11 tgl Exp $ * $PostgreSQL: pgsql/src/backend/executor/nodeSort.c,v 1.61 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -89,6 +89,8 @@ ExecSort(SortState *node) ...@@ -89,6 +89,8 @@ ExecSort(SortState *node)
plannode->nullsFirst, plannode->nullsFirst,
work_mem, work_mem,
node->randomAccess); node->randomAccess);
if (node->bounded)
tuplesort_set_bound(tuplesortstate, node->bound);
node->tuplesortstate = (void *) tuplesortstate; node->tuplesortstate = (void *) tuplesortstate;
/* /*
...@@ -119,6 +121,8 @@ ExecSort(SortState *node) ...@@ -119,6 +121,8 @@ ExecSort(SortState *node)
* finally set the sorted flag to true * finally set the sorted flag to true
*/ */
node->sort_Done = true; node->sort_Done = true;
node->bounded_Done = node->bounded;
node->bound_Done = node->bound;
SO1_printf("ExecSort: %s\n", "sorting done"); SO1_printf("ExecSort: %s\n", "sorting done");
} }
...@@ -167,6 +171,7 @@ ExecInitSort(Sort *node, EState *estate, int eflags) ...@@ -167,6 +171,7 @@ ExecInitSort(Sort *node, EState *estate, int eflags)
EXEC_FLAG_BACKWARD | EXEC_FLAG_BACKWARD |
EXEC_FLAG_MARK)) != 0; EXEC_FLAG_MARK)) != 0;
sortstate->bounded = false;
sortstate->sort_Done = false; sortstate->sort_Done = false;
sortstate->tuplesortstate = NULL; sortstate->tuplesortstate = NULL;
...@@ -307,11 +312,14 @@ ExecReScanSort(SortState *node, ExprContext *exprCtxt) ...@@ -307,11 +312,14 @@ ExecReScanSort(SortState *node, ExprContext *exprCtxt)
/* /*
* If subnode is to be rescanned then we forget previous sort results; we * If subnode is to be rescanned then we forget previous sort results; we
* have to re-read the subplan and re-sort. * have to re-read the subplan and re-sort. Also must re-sort if the
* bounded-sort parameters changed or we didn't select randomAccess.
* *
* Otherwise we can just rewind and rescan the sorted output. * Otherwise we can just rewind and rescan the sorted output.
*/ */
if (((PlanState *) node)->lefttree->chgParam != NULL || if (((PlanState *) node)->lefttree->chgParam != NULL ||
node->bounded != node->bounded_Done ||
node->bound != node->bound_Done ||
!node->randomAccess) !node->randomAccess)
{ {
node->sort_Done = false; node->sort_Done = false;
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.181 2007/04/21 21:01:44 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.182 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -922,6 +922,10 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) ...@@ -922,6 +922,10 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
* disk traffic = 2 * relsize * ceil(logM(p / (2*work_mem))) * disk traffic = 2 * relsize * ceil(logM(p / (2*work_mem)))
* cpu = comparison_cost * t * log2(t) * cpu = comparison_cost * t * log2(t)
* *
* If the sort is bounded (i.e., only the first k result tuples are needed)
* and k tuples can fit into work_mem, we use a heap method that keeps only
* k tuples in the heap; this will require about t*log2(k) tuple comparisons.
*
* The disk traffic is assumed to be 3/4ths sequential and 1/4th random * The disk traffic is assumed to be 3/4ths sequential and 1/4th random
* accesses (XXX can't we refine that guess?) * accesses (XXX can't we refine that guess?)
* *
...@@ -932,6 +936,7 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) ...@@ -932,6 +936,7 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
* 'input_cost' is the total cost for reading the input data * 'input_cost' is the total cost for reading the input data
* 'tuples' is the number of tuples in the relation * 'tuples' is the number of tuples in the relation
* 'width' is the average tuple width in bytes * 'width' is the average tuple width in bytes
* 'limit_tuples' is the bound on the number of output tuples; -1 if no bound
* *
* NOTE: some callers currently pass NIL for pathkeys because they * NOTE: some callers currently pass NIL for pathkeys because they
* can't conveniently supply the sort keys. Since this routine doesn't * can't conveniently supply the sort keys. Since this routine doesn't
...@@ -942,11 +947,14 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) ...@@ -942,11 +947,14 @@ cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
*/ */
void void
cost_sort(Path *path, PlannerInfo *root, cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width) List *pathkeys, Cost input_cost, double tuples, int width,
double limit_tuples)
{ {
Cost startup_cost = input_cost; Cost startup_cost = input_cost;
Cost run_cost = 0; Cost run_cost = 0;
double nbytes = relation_byte_size(tuples, width); double input_bytes = relation_byte_size(tuples, width);
double output_bytes;
double output_tuples;
long work_mem_bytes = work_mem * 1024L; long work_mem_bytes = work_mem * 1024L;
if (!enable_sort) if (!enable_sort)
...@@ -959,23 +967,39 @@ cost_sort(Path *path, PlannerInfo *root, ...@@ -959,23 +967,39 @@ cost_sort(Path *path, PlannerInfo *root,
if (tuples < 2.0) if (tuples < 2.0)
tuples = 2.0; tuples = 2.0;
/* /* Do we have a useful LIMIT? */
* CPU costs if (limit_tuples > 0 && limit_tuples < tuples)
* {
* Assume about two operator evals per tuple comparison and N log2 N output_tuples = limit_tuples;
* comparisons output_bytes = relation_byte_size(output_tuples, width);
*/ }
startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(tuples); else
{
output_tuples = tuples;
output_bytes = input_bytes;
}
/* disk costs */ if (output_bytes > work_mem_bytes)
if (nbytes > work_mem_bytes)
{ {
double npages = ceil(nbytes / BLCKSZ); /*
double nruns = (nbytes / work_mem_bytes) * 0.5; * We'll have to use a disk-based sort of all the tuples
*/
double npages = ceil(input_bytes / BLCKSZ);
double nruns = (input_bytes / work_mem_bytes) * 0.5;
double mergeorder = tuplesort_merge_order(work_mem_bytes); double mergeorder = tuplesort_merge_order(work_mem_bytes);
double log_runs; double log_runs;
double npageaccesses; double npageaccesses;
/*
* CPU costs
*
* Assume about two operator evals per tuple comparison and N log2 N
* comparisons
*/
startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(tuples);
/* Disk costs */
/* Compute logM(r) as log(r) / log(M) */ /* Compute logM(r) as log(r) / log(M) */
if (nruns > mergeorder) if (nruns > mergeorder)
log_runs = ceil(log(nruns) / log(mergeorder)); log_runs = ceil(log(nruns) / log(mergeorder));
...@@ -986,10 +1010,27 @@ cost_sort(Path *path, PlannerInfo *root, ...@@ -986,10 +1010,27 @@ cost_sort(Path *path, PlannerInfo *root,
startup_cost += npageaccesses * startup_cost += npageaccesses *
(seq_page_cost * 0.75 + random_page_cost * 0.25); (seq_page_cost * 0.75 + random_page_cost * 0.25);
} }
else if (tuples > 2 * output_tuples || input_bytes > work_mem_bytes)
{
/*
* We'll use a bounded heap-sort keeping just K tuples in memory,
* for a total number of tuple comparisons of N log2 K; but the
* constant factor is a bit higher than for quicksort. Tweak it
* so that the cost curve is continuous at the crossover point.
*/
startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(2.0 * output_tuples);
}
else
{
/* We'll use plain quicksort on all the input tuples */
startup_cost += 2.0 * cpu_operator_cost * tuples * LOG2(tuples);
}
/* /*
* Also charge a small amount (arbitrarily set equal to operator cost) per * Also charge a small amount (arbitrarily set equal to operator cost) per
* extracted tuple. * extracted tuple. Note it's correct to use tuples not output_tuples
* here --- the upper LIMIT will pro-rate the run cost so we'd be double
* counting the LIMIT otherwise.
*/ */
run_cost += cpu_operator_cost * tuples; run_cost += cpu_operator_cost * tuples;
...@@ -1431,7 +1472,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root) ...@@ -1431,7 +1472,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
outersortkeys, outersortkeys,
outer_path->total_cost, outer_path->total_cost,
outer_path_rows, outer_path_rows,
outer_path->parent->width); outer_path->parent->width,
-1.0);
startup_cost += sort_path.startup_cost; startup_cost += sort_path.startup_cost;
run_cost += (sort_path.total_cost - sort_path.startup_cost) run_cost += (sort_path.total_cost - sort_path.startup_cost)
* outerscansel; * outerscansel;
...@@ -1450,7 +1492,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root) ...@@ -1450,7 +1492,8 @@ cost_mergejoin(MergePath *path, PlannerInfo *root)
innersortkeys, innersortkeys,
inner_path->total_cost, inner_path->total_cost,
inner_path_rows, inner_path_rows,
inner_path->parent->width); inner_path->parent->width,
-1.0);
startup_cost += sort_path.startup_cost; startup_cost += sort_path.startup_cost;
run_cost += (sort_path.total_cost - sort_path.startup_cost) run_cost += (sort_path.total_cost - sort_path.startup_cost)
* innerscansel * rescanratio; * innerscansel * rescanratio;
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.229 2007/04/21 21:01:44 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.230 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -122,7 +122,8 @@ static MergeJoin *make_mergejoin(List *tlist, ...@@ -122,7 +122,8 @@ static MergeJoin *make_mergejoin(List *tlist,
Plan *lefttree, Plan *righttree, Plan *lefttree, Plan *righttree,
JoinType jointype); JoinType jointype);
static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols, static Sort *make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst); AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst,
double limit_tuples);
/* /*
...@@ -1579,7 +1580,8 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -1579,7 +1580,8 @@ create_mergejoin_plan(PlannerInfo *root,
outer_plan = (Plan *) outer_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
outer_plan, outer_plan,
best_path->outersortkeys); best_path->outersortkeys,
-1.0);
outerpathkeys = best_path->outersortkeys; outerpathkeys = best_path->outersortkeys;
} }
else else
...@@ -1591,7 +1593,8 @@ create_mergejoin_plan(PlannerInfo *root, ...@@ -1591,7 +1593,8 @@ create_mergejoin_plan(PlannerInfo *root,
inner_plan = (Plan *) inner_plan = (Plan *)
make_sort_from_pathkeys(root, make_sort_from_pathkeys(root,
inner_plan, inner_plan,
best_path->innersortkeys); best_path->innersortkeys,
-1.0);
innerpathkeys = best_path->innersortkeys; innerpathkeys = best_path->innersortkeys;
} }
else else
...@@ -2589,11 +2592,13 @@ make_mergejoin(List *tlist, ...@@ -2589,11 +2592,13 @@ make_mergejoin(List *tlist,
* make_sort --- basic routine to build a Sort plan node * make_sort --- basic routine to build a Sort plan node
* *
* Caller must have built the sortColIdx, sortOperators, and nullsFirst * Caller must have built the sortColIdx, sortOperators, and nullsFirst
* arrays already. * arrays already. limit_tuples is as for cost_sort (in particular, pass
* -1 if no limit)
*/ */
static Sort * static Sort *
make_sort(PlannerInfo *root, Plan *lefttree, int numCols, make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst) AttrNumber *sortColIdx, Oid *sortOperators, bool *nullsFirst,
double limit_tuples)
{ {
Sort *node = makeNode(Sort); Sort *node = makeNode(Sort);
Plan *plan = &node->plan; Plan *plan = &node->plan;
...@@ -2603,7 +2608,8 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols, ...@@ -2603,7 +2608,8 @@ make_sort(PlannerInfo *root, Plan *lefttree, int numCols,
cost_sort(&sort_path, root, NIL, cost_sort(&sort_path, root, NIL,
lefttree->total_cost, lefttree->total_cost,
lefttree->plan_rows, lefttree->plan_rows,
lefttree->plan_width); lefttree->plan_width,
limit_tuples);
plan->startup_cost = sort_path.startup_cost; plan->startup_cost = sort_path.startup_cost;
plan->total_cost = sort_path.total_cost; plan->total_cost = sort_path.total_cost;
plan->targetlist = lefttree->targetlist; plan->targetlist = lefttree->targetlist;
...@@ -2664,6 +2670,8 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, ...@@ -2664,6 +2670,8 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first,
* *
* 'lefttree' is the node which yields input tuples * 'lefttree' is the node which yields input tuples
* 'pathkeys' is the list of pathkeys by which the result is to be sorted * 'pathkeys' is the list of pathkeys by which the result is to be sorted
* 'limit_tuples' is the bound on the number of output tuples;
* -1 if no bound
* *
* We must convert the pathkey information into arrays of sort key column * We must convert the pathkey information into arrays of sort key column
* numbers and sort operator OIDs. * numbers and sort operator OIDs.
...@@ -2675,7 +2683,8 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first, ...@@ -2675,7 +2683,8 @@ add_sort_column(AttrNumber colIdx, Oid sortOp, bool nulls_first,
* adding a Result node just to do the projection. * adding a Result node just to do the projection.
*/ */
Sort * Sort *
make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
double limit_tuples)
{ {
List *tlist = lefttree->targetlist; List *tlist = lefttree->targetlist;
ListCell *i; ListCell *i;
...@@ -2810,7 +2819,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys) ...@@ -2810,7 +2819,7 @@ make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys)
Assert(numsortkeys > 0); Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys, return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators, nullsFirst); sortColIdx, sortOperators, nullsFirst, limit_tuples);
} }
/* /*
...@@ -2859,7 +2868,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree) ...@@ -2859,7 +2868,7 @@ make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, Plan *lefttree)
Assert(numsortkeys > 0); Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys, return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators, nullsFirst); sortColIdx, sortOperators, nullsFirst, -1.0);
} }
/* /*
...@@ -2919,7 +2928,7 @@ make_sort_from_groupcols(PlannerInfo *root, ...@@ -2919,7 +2928,7 @@ make_sort_from_groupcols(PlannerInfo *root,
Assert(numsortkeys > 0); Assert(numsortkeys > 0);
return make_sort(root, lefttree, numsortkeys, return make_sort(root, lefttree, numsortkeys,
sortColIdx, sortOperators, nullsFirst); sortColIdx, sortOperators, nullsFirst, -1.0);
} }
Material * Material *
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.100 2007/04/21 21:01:45 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planmain.c,v 1.101 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -47,6 +47,8 @@ ...@@ -47,6 +47,8 @@
* tlist is the target list the query should produce * tlist is the target list the query should produce
* (this is NOT necessarily root->parse->targetList!) * (this is NOT necessarily root->parse->targetList!)
* tuple_fraction is the fraction of tuples we expect will be retrieved * tuple_fraction is the fraction of tuples we expect will be retrieved
* limit_tuples is a hard limit on number of tuples to retrieve,
* or -1 if no limit
* *
* Output parameters: * Output parameters:
* *cheapest_path receives the overall-cheapest path for the query * *cheapest_path receives the overall-cheapest path for the query
...@@ -74,9 +76,13 @@ ...@@ -74,9 +76,13 @@
* from the plan to be retrieved * from the plan to be retrieved
* tuple_fraction >= 1: tuple_fraction is the absolute number of tuples * tuple_fraction >= 1: tuple_fraction is the absolute number of tuples
* expected to be retrieved (ie, a LIMIT specification) * expected to be retrieved (ie, a LIMIT specification)
* Note that a nonzero tuple_fraction could come from outer context; it is
* therefore not redundant with limit_tuples. We use limit_tuples to determine
* whether a bounded sort can be used at runtime.
*/ */
void void
query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, query_planner(PlannerInfo *root, List *tlist,
double tuple_fraction, double limit_tuples,
Path **cheapest_path, Path **sorted_path, Path **cheapest_path, Path **sorted_path,
double *num_groups) double *num_groups)
{ {
...@@ -354,7 +360,8 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction, ...@@ -354,7 +360,8 @@ query_planner(PlannerInfo *root, List *tlist, double tuple_fraction,
/* Figure cost for sorting */ /* Figure cost for sorting */
cost_sort(&sort_path, root, root->query_pathkeys, cost_sort(&sort_path, root, root->query_pathkeys,
cheapestpath->total_cost, cheapestpath->total_cost,
final_rel->rows, final_rel->width); final_rel->rows, final_rel->width,
limit_tuples);
} }
if (compare_fractional_path_costs(sortedpath, &sort_path, if (compare_fractional_path_costs(sortedpath, &sort_path,
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.218 2007/04/27 22:05:47 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.219 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -61,7 +61,8 @@ static double preprocess_limit(PlannerInfo *root, ...@@ -61,7 +61,8 @@ static double preprocess_limit(PlannerInfo *root,
double tuple_fraction, double tuple_fraction,
int64 *offset_est, int64 *count_est); int64 *offset_est, int64 *count_est);
static Oid *extract_grouping_ops(List *groupClause); static Oid *extract_grouping_ops(List *groupClause);
static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, static bool choose_hashed_grouping(PlannerInfo *root,
double tuple_fraction, double limit_tuples,
Path *cheapest_path, Path *sorted_path, Path *cheapest_path, Path *sorted_path,
Oid *groupOperators, double dNumGroups, Oid *groupOperators, double dNumGroups,
AggClauseCounts *agg_counts); AggClauseCounts *agg_counts);
...@@ -696,6 +697,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -696,6 +697,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
List *tlist = parse->targetList; List *tlist = parse->targetList;
int64 offset_est = 0; int64 offset_est = 0;
int64 count_est = 0; int64 count_est = 0;
double limit_tuples = -1.0;
Plan *result_plan; Plan *result_plan;
List *current_pathkeys; List *current_pathkeys;
List *sort_pathkeys; List *sort_pathkeys;
...@@ -703,8 +705,16 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -703,8 +705,16 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
/* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */ /* Tweak caller-supplied tuple_fraction if have LIMIT/OFFSET */
if (parse->limitCount || parse->limitOffset) if (parse->limitCount || parse->limitOffset)
{
tuple_fraction = preprocess_limit(root, tuple_fraction, tuple_fraction = preprocess_limit(root, tuple_fraction,
&offset_est, &count_est); &offset_est, &count_est);
/*
* If we have a known LIMIT, and don't have an unknown OFFSET,
* we can estimate the effects of using a bounded sort.
*/
if (count_est > 0 && offset_est >= 0)
limit_tuples = (double) count_est + (double) offset_est;
}
if (parse->setOperations) if (parse->setOperations)
{ {
...@@ -850,7 +860,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -850,7 +860,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* estimate the number of groups in the query, and canonicalize all * estimate the number of groups in the query, and canonicalize all
* the pathkeys. * the pathkeys.
*/ */
query_planner(root, sub_tlist, tuple_fraction, query_planner(root, sub_tlist, tuple_fraction, limit_tuples,
&cheapest_path, &sorted_path, &dNumGroups); &cheapest_path, &sorted_path, &dNumGroups);
group_pathkeys = root->group_pathkeys; group_pathkeys = root->group_pathkeys;
...@@ -864,7 +874,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -864,7 +874,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
{ {
groupOperators = extract_grouping_ops(parse->groupClause); groupOperators = extract_grouping_ops(parse->groupClause);
use_hashed_grouping = use_hashed_grouping =
choose_hashed_grouping(root, tuple_fraction, choose_hashed_grouping(root, tuple_fraction, limit_tuples,
cheapest_path, sorted_path, cheapest_path, sorted_path,
groupOperators, dNumGroups, groupOperators, dNumGroups,
&agg_counts); &agg_counts);
...@@ -1099,7 +1109,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1099,7 +1109,8 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
{ {
result_plan = (Plan *) make_sort_from_pathkeys(root, result_plan = (Plan *) make_sort_from_pathkeys(root,
result_plan, result_plan,
sort_pathkeys); sort_pathkeys,
limit_tuples);
current_pathkeys = sort_pathkeys; current_pathkeys = sort_pathkeys;
} }
} }
...@@ -1414,7 +1425,8 @@ extract_grouping_ops(List *groupClause) ...@@ -1414,7 +1425,8 @@ extract_grouping_ops(List *groupClause)
* choose_hashed_grouping - should we use hashed grouping? * choose_hashed_grouping - should we use hashed grouping?
*/ */
static bool static bool
choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, choose_hashed_grouping(PlannerInfo *root,
double tuple_fraction, double limit_tuples,
Path *cheapest_path, Path *sorted_path, Path *cheapest_path, Path *sorted_path,
Oid *groupOperators, double dNumGroups, Oid *groupOperators, double dNumGroups,
AggClauseCounts *agg_counts) AggClauseCounts *agg_counts)
...@@ -1499,7 +1511,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, ...@@ -1499,7 +1511,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
/* Result of hashed agg is always unsorted */ /* Result of hashed agg is always unsorted */
if (root->sort_pathkeys) if (root->sort_pathkeys)
cost_sort(&hashed_p, root, root->sort_pathkeys, hashed_p.total_cost, cost_sort(&hashed_p, root, root->sort_pathkeys, hashed_p.total_cost,
dNumGroups, cheapest_path_width); dNumGroups, cheapest_path_width, limit_tuples);
if (sorted_path) if (sorted_path)
{ {
...@@ -1516,7 +1528,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, ...@@ -1516,7 +1528,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
if (!pathkeys_contained_in(root->group_pathkeys, current_pathkeys)) if (!pathkeys_contained_in(root->group_pathkeys, current_pathkeys))
{ {
cost_sort(&sorted_p, root, root->group_pathkeys, sorted_p.total_cost, cost_sort(&sorted_p, root, root->group_pathkeys, sorted_p.total_cost,
cheapest_path_rows, cheapest_path_width); cheapest_path_rows, cheapest_path_width, -1.0);
current_pathkeys = root->group_pathkeys; current_pathkeys = root->group_pathkeys;
} }
...@@ -1533,7 +1545,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction, ...@@ -1533,7 +1545,7 @@ choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
if (root->sort_pathkeys && if (root->sort_pathkeys &&
!pathkeys_contained_in(root->sort_pathkeys, current_pathkeys)) !pathkeys_contained_in(root->sort_pathkeys, current_pathkeys))
cost_sort(&sorted_p, root, root->sort_pathkeys, sorted_p.total_cost, cost_sort(&sorted_p, root, root->sort_pathkeys, sorted_p.total_cost,
dNumGroups, cheapest_path_width); dNumGroups, cheapest_path_width, limit_tuples);
/* /*
* Now make the decision using the top-level tuple fraction. First we * Now make the decision using the top-level tuple fraction. First we
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.139 2007/04/21 21:01:45 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.140 2007/05/04 01:13:44 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -842,7 +842,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath) ...@@ -842,7 +842,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
cost_sort(&sort_path, root, NIL, cost_sort(&sort_path, root, NIL,
subpath->total_cost, subpath->total_cost,
rel->rows, rel->rows,
rel->width); rel->width,
-1.0);
/* /*
* Charge one cpu_operator_cost per comparison per input tuple. We assume * Charge one cpu_operator_cost per comparison per input tuple. We assume
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* Written by Peter Eisentraut <peter_e@gmx.net>. * Written by Peter Eisentraut <peter_e@gmx.net>.
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.389 2007/04/26 16:13:12 neilc Exp $ * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.390 2007/05/04 01:13:44 tgl Exp $
* *
*-------------------------------------------------------------------- *--------------------------------------------------------------------
*/ */
...@@ -108,6 +108,9 @@ extern bool fullPageWrites; ...@@ -108,6 +108,9 @@ extern bool fullPageWrites;
#ifdef TRACE_SORT #ifdef TRACE_SORT
extern bool trace_sort; extern bool trace_sort;
#endif #endif
#ifdef DEBUG_BOUNDED_SORT
extern bool optimize_bounded_sort;
#endif
#ifdef USE_SSL #ifdef USE_SSL
extern char *SSLCipherSuites; extern char *SSLCipherSuites;
...@@ -966,6 +969,20 @@ static struct config_bool ConfigureNamesBool[] = ...@@ -966,6 +969,20 @@ static struct config_bool ConfigureNamesBool[] =
}, },
#endif #endif
#ifdef DEBUG_BOUNDED_SORT
/* this is undocumented because not exposed in a standard build */
{
{
"optimize_bounded_sort", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enable bounded sorting using heap sort."),
NULL,
GUC_NOT_IN_SAMPLE
},
&optimize_bounded_sort,
true, NULL, NULL
},
#endif
#ifdef WAL_DEBUG #ifdef WAL_DEBUG
{ {
{"wal_debug", PGC_SUSET, DEVELOPER_OPTIONS, {"wal_debug", PGC_SUSET, DEVELOPER_OPTIONS,
...@@ -1711,7 +1728,7 @@ static struct config_int ConfigureNamesInt[] = ...@@ -1711,7 +1728,7 @@ static struct config_int ConfigureNamesInt[] =
&server_version_num, &server_version_num,
PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL PG_VERSION_NUM, PG_VERSION_NUM, PG_VERSION_NUM, NULL, NULL
}, },
{ {
{"log_temp_files", PGC_USERSET, LOGGING_WHAT, {"log_temp_files", PGC_USERSET, LOGGING_WHAT,
gettext_noop("Log the use of temporary files larger than this number of kilobytes."), gettext_noop("Log the use of temporary files larger than this number of kilobytes."),
...@@ -2883,7 +2900,7 @@ InitializeGUCOptions(void) ...@@ -2883,7 +2900,7 @@ InitializeGUCOptions(void)
PGC_S_DEFAULT)) PGC_S_DEFAULT))
elog(FATAL, "failed to initialize %s to %d", elog(FATAL, "failed to initialize %s to %d",
conf->gen.name, conf->boot_val); conf->gen.name, conf->boot_val);
*conf->variable = conf->reset_val = conf->boot_val; *conf->variable = conf->reset_val = conf->boot_val;
break; break;
} }
case PGC_REAL: case PGC_REAL:
...@@ -2897,7 +2914,7 @@ InitializeGUCOptions(void) ...@@ -2897,7 +2914,7 @@ InitializeGUCOptions(void)
PGC_S_DEFAULT)) PGC_S_DEFAULT))
elog(FATAL, "failed to initialize %s to %g", elog(FATAL, "failed to initialize %s to %g",
conf->gen.name, conf->boot_val); conf->gen.name, conf->boot_val);
*conf->variable = conf->reset_val = conf->boot_val; *conf->variable = conf->reset_val = conf->boot_val;
break; break;
} }
case PGC_STRING: case PGC_STRING:
......
This diff is collapsed.
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.172 2007/04/26 23:24:44 tgl Exp $ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.173 2007/05/04 01:13:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -1294,7 +1294,11 @@ typedef struct SortState ...@@ -1294,7 +1294,11 @@ typedef struct SortState
{ {
ScanState ss; /* its first field is NodeTag */ ScanState ss; /* its first field is NodeTag */
bool randomAccess; /* need random access to sort output? */ bool randomAccess; /* need random access to sort output? */
bool bounded; /* is the result set bounded? */
int64 bound; /* if bounded, how many tuples are needed */
bool sort_Done; /* sort completed yet? */ bool sort_Done; /* sort completed yet? */
bool bounded_Done; /* value of bounded we did the sort with */
int64 bound_Done; /* value of bound we did the sort with */
void *tuplesortstate; /* private state of tuplesort.c */ void *tuplesortstate; /* private state of tuplesort.c */
} SortState; } SortState;
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.85 2007/02/22 22:00:26 tgl Exp $ * $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.86 2007/05/04 01:13:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -73,7 +73,8 @@ extern void cost_functionscan(Path *path, PlannerInfo *root, ...@@ -73,7 +73,8 @@ extern void cost_functionscan(Path *path, PlannerInfo *root,
extern void cost_valuesscan(Path *path, PlannerInfo *root, extern void cost_valuesscan(Path *path, PlannerInfo *root,
RelOptInfo *baserel); RelOptInfo *baserel);
extern void cost_sort(Path *path, PlannerInfo *root, extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width); List *pathkeys, Cost input_cost, double tuples, int width,
double limit_tuples);
extern void cost_material(Path *path, extern void cost_material(Path *path,
Cost input_cost, double tuples, int width); Cost input_cost, double tuples, int width);
extern void cost_agg(Path *path, PlannerInfo *root, extern void cost_agg(Path *path, PlannerInfo *root,
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.100 2007/02/22 22:00:26 tgl Exp $ * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.101 2007/05/04 01:13:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
* prototypes for plan/planmain.c * prototypes for plan/planmain.c
*/ */
extern void query_planner(PlannerInfo *root, List *tlist, extern void query_planner(PlannerInfo *root, List *tlist,
double tuple_fraction, double tuple_fraction, double limit_tuples,
Path **cheapest_path, Path **sorted_path, Path **cheapest_path, Path **sorted_path,
double *num_groups); double *num_groups);
...@@ -39,7 +39,7 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, ...@@ -39,7 +39,7 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual,
Index scanrelid, Plan *subplan, List *subrtable); Index scanrelid, Plan *subplan, List *subrtable);
extern Append *make_append(List *appendplans, bool isTarget, List *tlist); extern Append *make_append(List *appendplans, bool isTarget, List *tlist);
extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, extern Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
List *pathkeys); List *pathkeys, double limit_tuples);
extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls, extern Sort *make_sort_from_sortclauses(PlannerInfo *root, List *sortcls,
Plan *lefttree); Plan *lefttree);
extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls, extern Sort *make_sort_from_groupcols(PlannerInfo *root, List *groupcls,
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.25 2007/01/09 02:14:16 tgl Exp $ * $PostgreSQL: pgsql/src/include/utils/tuplesort.h,v 1.26 2007/05/04 01:13:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -55,6 +55,8 @@ extern Tuplesortstate *tuplesort_begin_datum(Oid datumType, ...@@ -55,6 +55,8 @@ extern Tuplesortstate *tuplesort_begin_datum(Oid datumType,
Oid sortOperator, bool nullsFirstFlag, Oid sortOperator, bool nullsFirstFlag,
int workMem, bool randomAccess); int workMem, bool randomAccess);
extern void tuplesort_set_bound(Tuplesortstate *state, int64 bound);
extern void tuplesort_puttupleslot(Tuplesortstate *state, extern void tuplesort_puttupleslot(Tuplesortstate *state,
TupleTableSlot *slot); TupleTableSlot *slot);
extern void tuplesort_putindextuple(Tuplesortstate *state, IndexTuple tuple); extern void tuplesort_putindextuple(Tuplesortstate *state, IndexTuple tuple);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment