1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-21 00:42:43 +03:00

Add an explicit representation of the output targetlist to Paths.

Up to now, there's been an assumption that all Paths for a given relation
compute the same output column set (targetlist).  However, there are good
reasons to remove that assumption.  For example, an indexscan on an
expression index might be able to return the value of an expensive function
"for free".  While we have the ability to generate such a plan today in
simple cases, we don't have a way to model that it's cheaper than a plan
that computes the function from scratch, nor a way to create such a plan
in join cases (where the function computation would normally happen at
the topmost join node).  Also, we need this so that we can have Paths
representing post-scan/join steps, where the targetlist may well change
from one step to the next.  Therefore, invent a "struct PathTarget"
representing the columns we expect a plan step to emit.  It's convenient
to include the output tuple width and tlist evaluation cost in this struct,
and there will likely be additional fields in future.

While Path nodes that actually do have custom outputs will need their own
PathTargets, it will still be true that most Paths for a given relation
will compute the same tlist.  To reduce the overhead added by this patch,
keep a "default PathTarget" in RelOptInfo, and allow Paths that compute
that column set to just point to their parent RelOptInfo's reltarget.
(In the patch as committed, actually every Path is like that, since we
do not yet have any cases of custom PathTargets.)

I took this opportunity to provide some more-honest costing of
PlaceHolderVar evaluation.  Up to now, the assumption that "scan/join
reltargetlists have cost zero" was applied not only to Vars, where it's
reasonable, but also PlaceHolderVars where it isn't.  Now, we add the eval
cost of a PlaceHolderVar's expression to the first plan level where it can
be computed, by including it in the PathTarget cost field and adding that
to the cost estimates for Paths.  This isn't perfect yet but it's much
better than before, and there is a way forward to improve it more.  This
costing change affects the join order chosen for a couple of the regression
tests, changing expected row ordering.
This commit is contained in:
Tom Lane
2016-02-18 20:01:49 -05:00
parent 3386f34cdc
commit 19a541143a
18 changed files with 337 additions and 154 deletions

View File

@@ -182,8 +182,6 @@ clamp_row_est(double nrows)
*
* 'baserel' is the relation to be scanned
* 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
* 'nworkers' are the number of workers among which the work will be
* distributed if the scan is parallel scan
*/
void
cost_seqscan(Path *path, PlannerInfo *root,
@@ -225,6 +223,9 @@ cost_seqscan(Path *path, PlannerInfo *root,
startup_cost += qpqual_cost.startup;
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
cpu_run_cost = cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
cpu_run_cost += path->pathtarget->cost.per_tuple * path->rows;
/* Adjust costing for parallelism, if used. */
if (path->parallel_degree > 0)
@@ -335,6 +336,9 @@ cost_samplescan(Path *path, PlannerInfo *root,
startup_cost += qpqual_cost.startup;
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
@@ -601,6 +605,10 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
run_cost += cpu_per_tuple * tuples_fetched;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->path.pathtarget->cost.startup;
run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
path->path.startup_cost = startup_cost;
path->path.total_cost = startup_cost + run_cost;
}
@@ -910,6 +918,10 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
run_cost += cpu_per_tuple * tuples_fetched;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -1141,6 +1153,10 @@ cost_tidscan(Path *path, PlannerInfo *root,
tid_qual_cost.per_tuple;
run_cost += cpu_per_tuple * ntuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -1185,6 +1201,10 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost = cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost += startup_cost;
path->total_cost += startup_cost + run_cost;
}
@@ -1242,6 +1262,10 @@ cost_functionscan(Path *path, PlannerInfo *root,
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -1285,6 +1309,10 @@ cost_valuesscan(Path *path, PlannerInfo *root,
cpu_per_tuple += cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -1328,6 +1356,10 @@ cost_ctescan(Path *path, PlannerInfo *root,
cpu_per_tuple += cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost += cpu_per_tuple * baserel->tuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->pathtarget->cost.startup;
run_cost += path->pathtarget->cost.per_tuple * path->rows;
path->startup_cost = startup_cost;
path->total_cost = startup_cost + run_cost;
}
@@ -2080,6 +2112,10 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path,
cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple;
run_cost += cpu_per_tuple * ntuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->path.pathtarget->cost.startup;
run_cost += path->path.pathtarget->cost.per_tuple * path->path.rows;
path->path.startup_cost = startup_cost;
path->path.total_cost = startup_cost + run_cost;
}
@@ -2250,7 +2286,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
outersortkeys,
outer_path->total_cost,
outer_path_rows,
outer_path->parent->width,
outer_path->pathtarget->width,
0.0,
work_mem,
-1.0);
@@ -2276,7 +2312,7 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
innersortkeys,
inner_path->total_cost,
inner_path_rows,
inner_path->parent->width,
inner_path->pathtarget->width,
0.0,
work_mem,
-1.0);
@@ -2500,7 +2536,8 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
* off.
*/
else if (enable_material && innersortkeys != NIL &&
relation_byte_size(inner_path_rows, inner_path->parent->width) >
relation_byte_size(inner_path_rows,
inner_path->pathtarget->width) >
(work_mem * 1024L))
path->materialize_inner = true;
else
@@ -2539,6 +2576,10 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
cpu_per_tuple = cpu_tuple_cost + qp_qual_cost.per_tuple;
run_cost += cpu_per_tuple * mergejointuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->jpath.path.pathtarget->cost.startup;
run_cost += path->jpath.path.pathtarget->cost.per_tuple * path->jpath.path.rows;
path->jpath.path.startup_cost = startup_cost;
path->jpath.path.total_cost = startup_cost + run_cost;
}
@@ -2671,7 +2712,7 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
* optimization in the cost estimate, but for now, we don't.
*/
ExecChooseHashTableSize(inner_path_rows,
inner_path->parent->width,
inner_path->pathtarget->width,
true, /* useskew */
&numbuckets,
&numbatches,
@@ -2687,9 +2728,9 @@ initial_cost_hashjoin(PlannerInfo *root, JoinCostWorkspace *workspace,
if (numbatches > 1)
{
double outerpages = page_size(outer_path_rows,
outer_path->parent->width);
outer_path->pathtarget->width);
double innerpages = page_size(inner_path_rows,
inner_path->parent->width);
inner_path->pathtarget->width);
startup_cost += seq_page_cost * innerpages;
run_cost += seq_page_cost * (innerpages + 2 * outerpages);
@@ -2919,6 +2960,10 @@ final_cost_hashjoin(PlannerInfo *root, HashPath *path,
cpu_per_tuple = cpu_tuple_cost + qp_qual_cost.per_tuple;
run_cost += cpu_per_tuple * hashjointuples;
/* tlist eval costs are paid per output row, not per tuple scanned */
startup_cost += path->jpath.path.pathtarget->cost.startup;
run_cost += path->jpath.path.pathtarget->cost.per_tuple * path->jpath.path.rows;
path->jpath.path.startup_cost = startup_cost;
path->jpath.path.total_cost = startup_cost + run_cost;
}
@@ -3063,7 +3108,7 @@ cost_rescan(PlannerInfo *root, Path *path,
*/
Cost run_cost = cpu_tuple_cost * path->rows;
double nbytes = relation_byte_size(path->rows,
path->parent->width);
path->pathtarget->width);
long work_mem_bytes = work_mem * 1024L;
if (nbytes > work_mem_bytes)
@@ -3090,7 +3135,7 @@ cost_rescan(PlannerInfo *root, Path *path,
*/
Cost run_cost = cpu_operator_cost * path->rows;
double nbytes = relation_byte_size(path->rows,
path->parent->width);
path->pathtarget->width);
long work_mem_bytes = work_mem * 1024L;
if (nbytes > work_mem_bytes)
@@ -3356,6 +3401,20 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
return cost_qual_eval_walker((Node *) linitial(asplan->subplans),
context);
}
else if (IsA(node, PlaceHolderVar))
{
/*
* A PlaceHolderVar should be given cost zero when considering general
* expression evaluation costs. The expense of doing the contained
* expression is charged as part of the tlist eval costs of the scan
* or join where the PHV is first computed (see set_rel_width and
* add_placeholders_to_joinrel). If we charged it again here, we'd be
* double-counting the cost for each level of plan that the PHV
* bubbles up through. Hence, return without recursing into the
* phexpr.
*/
return false;
}
/* recurse into children */
return expression_tree_walker(node, cost_qual_eval_walker,
@@ -3751,7 +3810,7 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
* anyway we must keep the rowcount estimate the same for all paths for the
* joinrel.)
*
* We set only the rows field here. The width field was already set by
* We set only the rows field here. The reltarget field was already set by
* build_joinrel_tlist, and baserestrictcost is not used for join rels.
*/
void
@@ -4156,6 +4215,8 @@ set_foreign_size_estimates(PlannerInfo *root, RelOptInfo *rel)
* that have to be calculated at this relation. This is the amount of data
* we'd need to pass upwards in case of a sort, hash, etc.
*
* This function also sets reltarget.cost, so it's a bit misnamed now.
*
* NB: this works best on plain relations because it prefers to look at
* real Vars. For subqueries, set_subquery_size_estimates will already have
* copied up whatever per-column estimates were made within the subquery,
@@ -4174,12 +4235,16 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
bool have_wholerow_var = false;
ListCell *lc;
foreach(lc, rel->reltargetlist)
/* Vars are assumed to have cost zero, but other exprs do not */
rel->reltarget.cost.startup = 0;
rel->reltarget.cost.per_tuple = 0;
foreach(lc, rel->reltarget.exprs)
{
Node *node = (Node *) lfirst(lc);
/*
* Ordinarily, a Var in a rel's reltargetlist must belong to that rel;
* Ordinarily, a Var in a rel's targetlist must belong to that rel;
* but there are corner cases involving LATERAL references where that
* isn't so. If the Var has the wrong varno, fall through to the
* generic case (it doesn't seem worth the trouble to be any smarter).
@@ -4239,10 +4304,18 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
}
else if (IsA(node, PlaceHolderVar))
{
/*
* We will need to evaluate the PHV's contained expression while
* scanning this rel, so be sure to include it in reltarget.cost.
*/
PlaceHolderVar *phv = (PlaceHolderVar *) node;
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, false);
QualCost cost;
tuple_width += phinfo->ph_width;
cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
rel->reltarget.cost.startup += cost.startup;
rel->reltarget.cost.per_tuple += cost.per_tuple;
}
else
{
@@ -4252,10 +4325,15 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
* can using the expression type information.
*/
int32 item_width;
QualCost cost;
item_width = get_typavgwidth(exprType(node), exprTypmod(node));
Assert(item_width > 0);
tuple_width += item_width;
/* Not entirely clear if we need to account for cost, but do so */
cost_qual_eval_node(&cost, node, root);
rel->reltarget.cost.startup += cost.startup;
rel->reltarget.cost.per_tuple += cost.per_tuple;
}
}
@@ -4292,7 +4370,7 @@ set_rel_width(PlannerInfo *root, RelOptInfo *rel)
}
Assert(tuple_width >= 0);
rel->width = tuple_width;
rel->reltarget.width = tuple_width;
}
/*