Look into parameter value when estimating plan cost

Summary: Test cost estimator considers parameter values

Reviewers: florijan, mislav.bradac

Reviewed By: florijan

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D798
This commit is contained in:
Teon Banek 2017-09-15 14:52:02 +02:00
parent 960c5e6092
commit b31478dfd8
5 changed files with 89 additions and 57 deletions

View File

@ -117,7 +117,7 @@ class Interpreter {
ast_storage, ctx.symbol_table_, vertex_counts);
double min_cost = std::numeric_limits<double>::max();
for (auto &plan : plans) {
auto cost = EstimatePlanCost(vertex_counts, *plan);
auto cost = EstimatePlanCost(vertex_counts, ctx.parameters_, *plan);
if (!logical_plan || cost < min_cost) {
// We won't be iterating over plans anymore, so it's ok to invalidate
// unique_ptrs inside.
@ -130,7 +130,7 @@ class Interpreter {
logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
ast_storage, ctx.symbol_table_, vertex_counts);
query_plan_cost_estimation =
EstimatePlanCost(vertex_counts, *logical_plan);
EstimatePlanCost(vertex_counts, ctx.parameters_, *logical_plan);
}
// generate frame based on symbol table max_position

View File

@ -1,4 +1,5 @@
#include "query/frontend/ast/ast.hpp"
#include "query/parameters.hpp"
#include "query/plan/operator.hpp"
#include "query/typed_value.hpp"
@ -64,7 +65,8 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
using HierarchicalLogicalOperatorVisitor::PreVisit;
using HierarchicalLogicalOperatorVisitor::PostVisit;
CostEstimator(const TDbAccessor &db_accessor) : db_accessor_(db_accessor) {}
CostEstimator(const TDbAccessor &db_accessor, const Parameters &parameters)
: db_accessor_(db_accessor), parameters(parameters) {}
bool PostVisit(ScanAll &) override {
cardinality_ *= db_accessor_.VerticesCount();
@ -81,17 +83,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
}
bool PostVisit(ScanAllByLabelPropertyValue &logical_op) override {
// this cardinality estimation depends on the property value (expression).
// if it's a literal (const) we can evaluate cardinality exactly, otherwise
// This cardinality estimation depends on the property value (expression).
// If it's a constant, we can evaluate cardinality exactly, otherwise
// we estimate
std::experimental::optional<PropertyValue> property_value =
std::experimental::nullopt;
if (auto *literal =
dynamic_cast<PrimitiveLiteral *>(logical_op.expression()))
if (literal->value_.IsPropertyValue())
property_value =
std::experimental::optional<PropertyValue>(literal->value_);
auto property_value = ConstPropertyValue(logical_op.expression());
double factor = 1.0;
if (property_value)
// get the exact influence based on ScanAll(label, property, value)
@ -206,27 +201,43 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
// accessor used for cardinality estimates in ScanAll and ScanAllByLabel
const TDbAccessor &db_accessor_;
const Parameters &parameters;
void IncrementCost(double param) { cost_ += param * cardinality_; }
// converts an optional ScanAll range bound into a property value
// if the bound is present and is a literal expression convertible to
// if the bound is present and is a constant expression convertible to
// a property value. otherwise returns nullopt
static std::experimental::optional<utils::Bound<PropertyValue>>
BoundToPropertyValue(
std::experimental::optional<utils::Bound<PropertyValue>> BoundToPropertyValue(
std::experimental::optional<ScanAllByLabelPropertyRange::Bound> bound) {
if (bound)
if (auto *literal = dynamic_cast<PrimitiveLiteral *>(bound->value()))
return std::experimental::make_optional(
utils::Bound<PropertyValue>(literal->value_, bound->type()));
if (bound) {
auto property_value = ConstPropertyValue(bound->value());
if (property_value)
return utils::Bound<PropertyValue>(*property_value, bound->type());
}
return std::experimental::nullopt;
}
// If the expression is a constant property value, it is returned. Otherwise,
// return nullopt.
std::experimental::optional<PropertyValue> ConstPropertyValue(
const Expression *expression) {
if (auto *literal = dynamic_cast<const PrimitiveLiteral *>(expression)) {
if (literal->value_.IsPropertyValue()) return literal->value_;
} else if (auto *param_lookup =
dynamic_cast<const ParameterLookup *>(expression)) {
auto value = parameters.AtTokenPosition(param_lookup->token_position_);
if (value.IsPropertyValue()) return value;
}
return std::experimental::nullopt;
}
};
/** Returns the estimated cost of the given plan. */
template <class TDbAccessor>
double EstimatePlanCost(const TDbAccessor &db, LogicalOperator &plan) {
CostEstimator<TDbAccessor> estimator(db);
double EstimatePlanCost(const TDbAccessor &db, const Parameters &parameters,
LogicalOperator &plan) {
CostEstimator<TDbAccessor> estimator(db, parameters);
plan.Accept(estimator);
return estimator.cost();
}

View File

@ -95,6 +95,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
std::tie(label, prop) =
CreateIndexedVertices(index_count, vertex_count, dbms);
auto dba = dbms.active();
Parameters parameters;
while (state.KeepRunning()) {
state.PauseTiming();
query::AstTreeStorage storage;
@ -108,7 +109,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
storage, symbol_table, *dba);
for (auto &plan : plans) {
query::plan::EstimatePlanCost(*dba, *plan);
query::plan::EstimatePlanCost(*dba, parameters, *plan);
}
}
}
@ -124,6 +125,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(
CreateIndexedVertices(index_count, vertex_count, dbms);
auto dba = dbms.active();
auto vertex_counts = query::plan::MakeVertexCountCache(*dba);
Parameters parameters;
while (state.KeepRunning()) {
state.PauseTiming();
query::AstTreeStorage storage;
@ -137,7 +139,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
storage, symbol_table, vertex_counts);
for (auto &plan : plans) {
query::plan::EstimatePlanCost(vertex_counts, *plan);
query::plan::EstimatePlanCost(vertex_counts, parameters, *plan);
}
}
}

View File

@ -613,8 +613,10 @@ auto MakeLogicalPlans(query::AstTreeStorage &ast,
plans_with_cost;
auto plans = query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
ast, symbol_table, dba);
Parameters parameters;
for (auto &plan : plans) {
query::plan::CostEstimator<InteractiveDbAccessor> estimator(dba);
query::plan::CostEstimator<InteractiveDbAccessor> estimator(dba,
parameters);
plan->Accept(estimator);
plans_with_cost.emplace_back(std::move(plan), estimator.cost());
}

View File

@ -33,6 +33,7 @@ class QueryCostEstimator : public ::testing::Test {
AstTreeStorage storage_;
SymbolTable symbol_table_;
Parameters parameters_;
int symbol_count = 0;
void SetUp() {
@ -61,7 +62,7 @@ class QueryCostEstimator : public ::testing::Test {
}
auto Cost() {
CostEstimator<GraphDbAccessor> cost_estimator(*dba);
CostEstimator<GraphDbAccessor> cost_estimator(*dba, parameters_);
last_op_->Accept(cost_estimator);
return cost_estimator.cost();
}
@ -76,9 +77,15 @@ class QueryCostEstimator : public ::testing::Test {
return storage_.Create<PrimitiveLiteral>(value);
}
auto InclusiveBound(int bound) {
Expression *Parameter(const TypedValue &value) {
int token_position = parameters_.size();
parameters_.Add(token_position, value);
return storage_.Create<ParameterLookup>(token_position);
}
auto InclusiveBound(Expression *expression) {
return std::experimental::make_optional(
utils::MakeBoundInclusive(Literal(bound)));
utils::MakeBoundInclusive(expression));
};
const std::experimental::nullopt_t nullopt = std::experimental::nullopt;
@ -101,48 +108,58 @@ TEST_F(QueryCostEstimator, ScanAllByLabelCardinality) {
EXPECT_COST(30 * CostParam::kScanAllByLabel);
}
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueLiteral) {
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstant) {
AddVertices(100, 30, 20);
MakeOp<ScanAllByLabelPropertyValue>(last_op_, NextSymbol(), label, property,
Literal(12));
EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue);
for (auto const_val : {Literal(12), Parameter(12)}) {
MakeOp<ScanAllByLabelPropertyValue>(nullptr, NextSymbol(), label, property,
const_val);
EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue);
}
}
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueExpr) {
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstExpr) {
AddVertices(100, 30, 20);
MakeOp<ScanAllByLabelPropertyValue>(
last_op_, NextSymbol(), label, property,
// once we make expression const-folding this test case will fail
storage_.Create<UnaryPlusOperator>(Literal(12)));
EXPECT_COST(20 * CardParam::kFilter *
CostParam::MakeScanAllByLabelPropertyValue);
for (auto const_val : {Literal(12), Parameter(12)}) {
MakeOp<ScanAllByLabelPropertyValue>(
nullptr, NextSymbol(), label, property,
// once we make expression const-folding this test case will fail
storage_.Create<UnaryPlusOperator>(const_val));
EXPECT_COST(20 * CardParam::kFilter *
CostParam::MakeScanAllByLabelPropertyValue);
}
}
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpper) {
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpperConstant) {
AddVertices(100, 30, 20);
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
nullopt, InclusiveBound(12));
// cardinality estimation is exact for very small indexes
EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange);
for (auto const_val : {Literal(12), Parameter(12)}) {
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
nullopt, InclusiveBound(const_val));
// cardinality estimation is exact for very small indexes
EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange);
}
}
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLower) {
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLowerConstant) {
AddVertices(100, 30, 20);
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
InclusiveBound(17), nullopt);
// cardinality estimation is exact for very small indexes
EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange);
for (auto const_val : {Literal(17), Parameter(17)}) {
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
InclusiveBound(const_val), nullopt);
// cardinality estimation is exact for very small indexes
EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange);
}
}
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeNonLiteral) {
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeConstExpr) {
AddVertices(100, 30, 20);
auto bound = std::experimental::make_optional(
utils::MakeBoundInclusive(static_cast<Expression *>(
storage_.Create<UnaryPlusOperator>(Literal(12)))));
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
bound, nullopt);
EXPECT_COST(20 * CardParam::kFilter *
CostParam::MakeScanAllByLabelPropertyRange);
for (auto const_val : {Literal(12), Parameter(12)}) {
auto bound = std::experimental::make_optional(
utils::MakeBoundInclusive(static_cast<Expression *>(
storage_.Create<UnaryPlusOperator>(const_val))));
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
bound, nullopt);
EXPECT_COST(20 * CardParam::kFilter *
CostParam::MakeScanAllByLabelPropertyRange);
}
}
TEST_F(QueryCostEstimator, Expand) {