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:
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user