Use custom allocator in Evaluator through context
Summary: Micro benchmarks show improvements in performance of MapLiteral from 5% to 40% depending on the size of the input. On the other hand, a sequence of AdditionOperators behaves the same with both allocation schemes. Reviewers: mtomic, mferencevic, msantl Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2132
This commit is contained in:
parent
57d967786c
commit
3fd14e2d5f
@ -22,6 +22,8 @@ ProduceRpcServer::OngoingProduce::OngoingProduce(
|
|||||||
query::kExecutionMemoryBlockSize)),
|
query::kExecutionMemoryBlockSize)),
|
||||||
cursor_(plan_pack.plan->MakeCursor(dba_.get(), execution_memory_.get())) {
|
cursor_(plan_pack.plan->MakeCursor(dba_.get(), execution_memory_.get())) {
|
||||||
context_.symbol_table = plan_pack.symbol_table;
|
context_.symbol_table = plan_pack.symbol_table;
|
||||||
|
// TODO: Maybe we want a seperate MemoryResource per pull evaluation
|
||||||
|
context_.evaluation_context.memory = execution_memory_.get();
|
||||||
context_.evaluation_context.timestamp = timestamp;
|
context_.evaluation_context.timestamp = timestamp;
|
||||||
context_.evaluation_context.parameters = parameters;
|
context_.evaluation_context.parameters = parameters;
|
||||||
context_.evaluation_context.properties =
|
context_.evaluation_context.properties =
|
||||||
|
@ -10,6 +10,12 @@ namespace query {
|
|||||||
static constexpr size_t kExecutionMemoryBlockSize = 1U * 1024U * 1024U;
|
static constexpr size_t kExecutionMemoryBlockSize = 1U * 1024U * 1024U;
|
||||||
|
|
||||||
struct EvaluationContext {
|
struct EvaluationContext {
|
||||||
|
/// Memory for allocations during evaluation of a *single* Pull call.
|
||||||
|
///
|
||||||
|
/// Although the assigned memory may live longer than the duration of a Pull
|
||||||
|
/// (e.g. memory is the same as the whole execution memory), you have to treat
|
||||||
|
/// it as if the lifetime is only valid during the Pull.
|
||||||
|
utils::MemoryResource *memory{utils::NewDeleteResource()};
|
||||||
int64_t timestamp{-1};
|
int64_t timestamp{-1};
|
||||||
Parameters parameters;
|
Parameters parameters;
|
||||||
/// All properties indexable via PropertyIx
|
/// All properties indexable via PropertyIx
|
||||||
|
@ -32,6 +32,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
|
|
||||||
using ExpressionVisitor<TypedValue>::Visit;
|
using ExpressionVisitor<TypedValue>::Visit;
|
||||||
|
|
||||||
|
utils::MemoryResource *GetMemoryResource() const { return ctx_->memory; }
|
||||||
|
|
||||||
TypedValue Visit(NamedExpression &named_expression) override {
|
TypedValue Visit(NamedExpression &named_expression) override {
|
||||||
const auto &symbol = symbol_table_->at(named_expression);
|
const auto &symbol = symbol_table_->at(named_expression);
|
||||||
auto value = named_expression.expression_->Accept(*this);
|
auto value = named_expression.expression_->Accept(*this);
|
||||||
@ -40,7 +42,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(Identifier &ident) override {
|
TypedValue Visit(Identifier &ident) override {
|
||||||
auto value = frame_->at(symbol_table_->at(ident));
|
TypedValue value(frame_->at(symbol_table_->at(ident)), ctx_->memory);
|
||||||
SwitchAccessors(value);
|
SwitchAccessors(value);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -124,21 +126,21 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
auto literal = in_list.expression1_->Accept(*this);
|
auto literal = in_list.expression1_->Accept(*this);
|
||||||
auto _list = in_list.expression2_->Accept(*this);
|
auto _list = in_list.expression2_->Accept(*this);
|
||||||
if (_list.IsNull()) {
|
if (_list.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
// Exceptions have higher priority than returning nulls when list expression
|
// Exceptions have higher priority than returning nulls when list expression
|
||||||
// is not null.
|
// is not null.
|
||||||
if (_list.type() != TypedValue::Type::List) {
|
if (_list.type() != TypedValue::Type::List) {
|
||||||
throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
|
throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
|
||||||
}
|
}
|
||||||
auto list = _list.ValueList();
|
const auto &list = _list.ValueList();
|
||||||
|
|
||||||
// If literal is NULL there is no need to try to compare it with every
|
// If literal is NULL there is no need to try to compare it with every
|
||||||
// element in the list since result of every comparison will be NULL. There
|
// element in the list since result of every comparison will be NULL. There
|
||||||
// is one special case that we must test explicitly: if list is empty then
|
// is one special case that we must test explicitly: if list is empty then
|
||||||
// result is false since no comparison will be performed.
|
// result is false since no comparison will be performed.
|
||||||
if (list.empty()) return TypedValue(false);
|
if (list.empty()) return TypedValue(false, ctx_->memory);
|
||||||
if (literal.IsNull()) return TypedValue();
|
if (literal.IsNull()) return TypedValue(ctx_->memory);
|
||||||
|
|
||||||
auto has_null = false;
|
auto has_null = false;
|
||||||
for (const auto &element : list) {
|
for (const auto &element : list) {
|
||||||
@ -146,13 +148,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
if (result.IsNull()) {
|
if (result.IsNull()) {
|
||||||
has_null = true;
|
has_null = true;
|
||||||
} else if (result.Value<bool>()) {
|
} else if (result.Value<bool>()) {
|
||||||
return TypedValue(true);
|
return TypedValue(true, ctx_->memory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (has_null) {
|
if (has_null) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
return TypedValue(false);
|
return TypedValue(false, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(SubscriptOperator &list_indexing) override {
|
TypedValue Visit(SubscriptOperator &list_indexing) override {
|
||||||
@ -164,29 +166,37 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
"Expected a list, a map, a node or an edge to index with '[]', got "
|
"Expected a list, a map, a node or an edge to index with '[]', got "
|
||||||
"{}.",
|
"{}.",
|
||||||
lhs.type());
|
lhs.type());
|
||||||
if (lhs.IsNull() || index.IsNull()) return TypedValue();
|
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
||||||
if (lhs.IsList()) {
|
if (lhs.IsList()) {
|
||||||
if (!index.IsInt())
|
if (!index.IsInt())
|
||||||
throw QueryRuntimeException(
|
throw QueryRuntimeException(
|
||||||
"Expected an integer as a list index, got {}.", index.type());
|
"Expected an integer as a list index, got {}.", index.type());
|
||||||
auto index_int = index.Value<int64_t>();
|
auto index_int = index.Value<int64_t>();
|
||||||
const auto &list = lhs.ValueList();
|
// NOTE: Take non-const reference to list, so that we can move out the
|
||||||
|
// indexed element as the result.
|
||||||
|
auto &list = lhs.ValueList();
|
||||||
if (index_int < 0) {
|
if (index_int < 0) {
|
||||||
index_int += static_cast<int64_t>(list.size());
|
index_int += static_cast<int64_t>(list.size());
|
||||||
}
|
}
|
||||||
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0)
|
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0)
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
return list[index_int];
|
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||||
|
// value and preserve the correct MemoryResource.
|
||||||
|
return std::move(list[index_int]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lhs.IsMap()) {
|
if (lhs.IsMap()) {
|
||||||
if (!index.IsString())
|
if (!index.IsString())
|
||||||
throw QueryRuntimeException("Expected a string as a map index, got {}.",
|
throw QueryRuntimeException("Expected a string as a map index, got {}.",
|
||||||
index.type());
|
index.type());
|
||||||
const auto &map = lhs.ValueMap();
|
// NOTE: Take non-const reference to map, so that we can move out the
|
||||||
|
// looked-up element as the result.
|
||||||
|
auto &map = lhs.ValueMap();
|
||||||
auto found = map.find(index.ValueString());
|
auto found = map.find(index.ValueString());
|
||||||
if (found == map.end()) return TypedValue();
|
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||||
return found->second;
|
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||||
|
// value and preserve the correct MemoryResource.
|
||||||
|
return std::move(found->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lhs.IsVertex()) {
|
if (lhs.IsVertex()) {
|
||||||
@ -194,7 +204,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
throw QueryRuntimeException(
|
throw QueryRuntimeException(
|
||||||
"Expected a string as a property name, got {}.", index.type());
|
"Expected a string as a property name, got {}.", index.type());
|
||||||
return TypedValue(lhs.Value<VertexAccessor>().PropsAt(
|
return TypedValue(lhs.Value<VertexAccessor>().PropsAt(
|
||||||
dba_->Property(std::string(index.ValueString()))));
|
dba_->Property(std::string(index.ValueString()))),
|
||||||
|
ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lhs.IsEdge()) {
|
if (lhs.IsEdge()) {
|
||||||
@ -202,11 +213,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
throw QueryRuntimeException(
|
throw QueryRuntimeException(
|
||||||
"Expected a string as a property name, got {}.", index.type());
|
"Expected a string as a property name, got {}.", index.type());
|
||||||
return TypedValue(lhs.Value<EdgeAccessor>().PropsAt(
|
return TypedValue(lhs.Value<EdgeAccessor>().PropsAt(
|
||||||
dba_->Property(std::string(index.ValueString()))));
|
dba_->Property(std::string(index.ValueString()))),
|
||||||
|
ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lhs is Null
|
// lhs is Null
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(ListSlicingOperator &op) override {
|
TypedValue Visit(ListSlicingOperator &op) override {
|
||||||
@ -225,7 +237,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
return bound;
|
return bound;
|
||||||
}
|
}
|
||||||
return TypedValue(default_value);
|
return TypedValue(default_value, ctx_->memory);
|
||||||
};
|
};
|
||||||
auto _upper_bound =
|
auto _upper_bound =
|
||||||
get_bound(op.upper_bound_, std::numeric_limits<int64_t>::max());
|
get_bound(op.upper_bound_, std::numeric_limits<int64_t>::max());
|
||||||
@ -240,7 +252,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_null) {
|
if (is_null) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
const auto &list = _list.ValueList();
|
const auto &list = _list.ValueList();
|
||||||
auto normalise_bound = [&](int64_t bound) {
|
auto normalise_bound = [&](int64_t bound) {
|
||||||
@ -253,33 +265,39 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
auto lower_bound = normalise_bound(_lower_bound.Value<int64_t>());
|
auto lower_bound = normalise_bound(_lower_bound.Value<int64_t>());
|
||||||
auto upper_bound = normalise_bound(_upper_bound.Value<int64_t>());
|
auto upper_bound = normalise_bound(_upper_bound.Value<int64_t>());
|
||||||
if (upper_bound <= lower_bound) {
|
if (upper_bound <= lower_bound) {
|
||||||
return TypedValue(std::vector<TypedValue>());
|
return TypedValue(TypedValue::TVector(ctx_->memory), ctx_->memory);
|
||||||
}
|
}
|
||||||
return TypedValue(std::vector<TypedValue>(list.begin() + lower_bound,
|
return TypedValue(TypedValue::TVector(
|
||||||
list.begin() + upper_bound));
|
list.begin() + lower_bound, list.begin() + upper_bound, ctx_->memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(IsNullOperator &is_null) override {
|
TypedValue Visit(IsNullOperator &is_null) override {
|
||||||
auto value = is_null.expression_->Accept(*this);
|
auto value = is_null.expression_->Accept(*this);
|
||||||
return TypedValue(value.IsNull());
|
return TypedValue(value.IsNull(), ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(PropertyLookup &property_lookup) override {
|
TypedValue Visit(PropertyLookup &property_lookup) override {
|
||||||
auto expression_result = property_lookup.expression_->Accept(*this);
|
auto expression_result = property_lookup.expression_->Accept(*this);
|
||||||
switch (expression_result.type()) {
|
switch (expression_result.type()) {
|
||||||
case TypedValue::Type::Null:
|
case TypedValue::Type::Null:
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
case TypedValue::Type::Vertex:
|
case TypedValue::Type::Vertex:
|
||||||
return TypedValue(expression_result.Value<VertexAccessor>().PropsAt(
|
return TypedValue(expression_result.Value<VertexAccessor>().PropsAt(
|
||||||
GetProperty(property_lookup.property_)));
|
GetProperty(property_lookup.property_)),
|
||||||
|
ctx_->memory);
|
||||||
case TypedValue::Type::Edge:
|
case TypedValue::Type::Edge:
|
||||||
return TypedValue(expression_result.Value<EdgeAccessor>().PropsAt(
|
return TypedValue(expression_result.Value<EdgeAccessor>().PropsAt(
|
||||||
GetProperty(property_lookup.property_)));
|
GetProperty(property_lookup.property_)),
|
||||||
|
ctx_->memory);
|
||||||
case TypedValue::Type::Map: {
|
case TypedValue::Type::Map: {
|
||||||
const auto &map = expression_result.ValueMap();
|
// NOTE: Take non-const reference to map, so that we can move out the
|
||||||
|
// looked-up element as the result.
|
||||||
|
auto &map = expression_result.ValueMap();
|
||||||
auto found = map.find(property_lookup.property_.name.c_str());
|
auto found = map.find(property_lookup.property_.name.c_str());
|
||||||
if (found == map.end()) return TypedValue();
|
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||||
return found->second;
|
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||||
|
// value and preserve the correct MemoryResource.
|
||||||
|
return std::move(found->second);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw QueryRuntimeException(
|
throw QueryRuntimeException(
|
||||||
@ -291,15 +309,15 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
auto expression_result = labels_test.expression_->Accept(*this);
|
auto expression_result = labels_test.expression_->Accept(*this);
|
||||||
switch (expression_result.type()) {
|
switch (expression_result.type()) {
|
||||||
case TypedValue::Type::Null:
|
case TypedValue::Type::Null:
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
case TypedValue::Type::Vertex: {
|
case TypedValue::Type::Vertex: {
|
||||||
auto vertex = expression_result.Value<VertexAccessor>();
|
const auto &vertex = expression_result.Value<VertexAccessor>();
|
||||||
for (const auto label : labels_test.labels_) {
|
for (const auto &label : labels_test.labels_) {
|
||||||
if (!vertex.has_label(GetLabel(label))) {
|
if (!vertex.has_label(GetLabel(label))) {
|
||||||
return TypedValue(false);
|
return TypedValue(false, ctx_->memory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TypedValue(true);
|
return TypedValue(true, ctx_->memory);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw QueryRuntimeException("Only nodes have labels.");
|
throw QueryRuntimeException("Only nodes have labels.");
|
||||||
@ -309,26 +327,26 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
TypedValue Visit(PrimitiveLiteral &literal) override {
|
TypedValue Visit(PrimitiveLiteral &literal) override {
|
||||||
// TODO: no need to evaluate constants, we can write it to frame in one
|
// TODO: no need to evaluate constants, we can write it to frame in one
|
||||||
// of the previous phases.
|
// of the previous phases.
|
||||||
return TypedValue(literal.value_);
|
return TypedValue(literal.value_, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(ListLiteral &literal) override {
|
TypedValue Visit(ListLiteral &literal) override {
|
||||||
std::vector<TypedValue> result;
|
TypedValue::TVector result(ctx_->memory);
|
||||||
result.reserve(literal.elements_.size());
|
result.reserve(literal.elements_.size());
|
||||||
for (const auto &expression : literal.elements_)
|
for (const auto &expression : literal.elements_)
|
||||||
result.emplace_back(expression->Accept(*this));
|
result.emplace_back(expression->Accept(*this));
|
||||||
return TypedValue(result);
|
return TypedValue(result, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(MapLiteral &literal) override {
|
TypedValue Visit(MapLiteral &literal) override {
|
||||||
std::map<std::string, TypedValue> result;
|
TypedValue::TMap result(ctx_->memory);
|
||||||
for (const auto &pair : literal.elements_)
|
for (const auto &pair : literal.elements_)
|
||||||
result.emplace(pair.first.name, pair.second->Accept(*this));
|
result.emplace(pair.first.name, pair.second->Accept(*this));
|
||||||
return TypedValue(result);
|
return TypedValue(result, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(Aggregation &aggregation) override {
|
TypedValue Visit(Aggregation &aggregation) override {
|
||||||
auto value = frame_->at(symbol_table_->at(aggregation));
|
TypedValue value(frame_->at(symbol_table_->at(aggregation)), ctx_->memory);
|
||||||
// Aggregation is probably always simple type, but let's switch accessor
|
// Aggregation is probably always simple type, but let's switch accessor
|
||||||
// just to be sure.
|
// just to be sure.
|
||||||
SwitchAccessors(value);
|
SwitchAccessors(value);
|
||||||
@ -343,39 +361,46 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int64_t i = 0; i < exprs.size(); ++i) {
|
for (int64_t i = 0; i < exprs.size(); ++i) {
|
||||||
TypedValue val = exprs[i]->Accept(*this);
|
TypedValue val(exprs[i]->Accept(*this), ctx_->memory);
|
||||||
if (!val.IsNull()) {
|
if (!val.IsNull()) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(Function &function) override {
|
TypedValue Visit(Function &function) override {
|
||||||
// Stack allocate evaluated arguments when there's a small number of them.
|
// Stack allocate evaluated arguments when there's a small number of them.
|
||||||
if (function.arguments_.size() <= 8) {
|
if (function.arguments_.size() <= 8) {
|
||||||
TypedValue arguments[8];
|
TypedValue arguments[8] = {
|
||||||
|
TypedValue(ctx_->memory), TypedValue(ctx_->memory),
|
||||||
|
TypedValue(ctx_->memory), TypedValue(ctx_->memory),
|
||||||
|
TypedValue(ctx_->memory), TypedValue(ctx_->memory),
|
||||||
|
TypedValue(ctx_->memory), TypedValue(ctx_->memory)};
|
||||||
for (size_t i = 0; i < function.arguments_.size(); ++i) {
|
for (size_t i = 0; i < function.arguments_.size(); ++i) {
|
||||||
arguments[i] = function.arguments_[i]->Accept(*this);
|
arguments[i] = function.arguments_[i]->Accept(*this);
|
||||||
}
|
}
|
||||||
return function.function_(arguments, function.arguments_.size(), *ctx_,
|
// TODO: Update awesome_memgraph_functions to use the allocator from ctx_
|
||||||
dba_);
|
return TypedValue(function.function_(
|
||||||
|
arguments, function.arguments_.size(), *ctx_, dba_),
|
||||||
|
ctx_->memory);
|
||||||
} else {
|
} else {
|
||||||
std::vector<TypedValue> arguments;
|
TypedValue::TVector arguments(ctx_->memory);
|
||||||
arguments.reserve(function.arguments_.size());
|
arguments.reserve(function.arguments_.size());
|
||||||
for (const auto &argument : function.arguments_) {
|
for (const auto &argument : function.arguments_) {
|
||||||
arguments.emplace_back(argument->Accept(*this));
|
arguments.emplace_back(argument->Accept(*this));
|
||||||
}
|
}
|
||||||
return function.function_(arguments.data(), arguments.size(), *ctx_,
|
// TODO: Update awesome_memgraph_functions to use the allocator from ctx_
|
||||||
dba_);
|
return TypedValue(
|
||||||
|
function.function_(arguments.data(), arguments.size(), *ctx_, dba_),
|
||||||
|
ctx_->memory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(Reduce &reduce) override {
|
TypedValue Visit(Reduce &reduce) override {
|
||||||
auto list_value = reduce.list_->Accept(*this);
|
auto list_value = reduce.list_->Accept(*this);
|
||||||
if (list_value.IsNull()) {
|
if (list_value.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
if (list_value.type() != TypedValue::Type::List) {
|
||||||
throw QueryRuntimeException("REDUCE expected a list, got {}.",
|
throw QueryRuntimeException("REDUCE expected a list, got {}.",
|
||||||
@ -396,7 +421,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
TypedValue Visit(Extract &extract) override {
|
TypedValue Visit(Extract &extract) override {
|
||||||
auto list_value = extract.list_->Accept(*this);
|
auto list_value = extract.list_->Accept(*this);
|
||||||
if (list_value.IsNull()) {
|
if (list_value.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
if (list_value.type() != TypedValue::Type::List) {
|
||||||
throw QueryRuntimeException("EXTRACT expected a list, got {}.",
|
throw QueryRuntimeException("EXTRACT expected a list, got {}.",
|
||||||
@ -404,23 +429,23 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
const auto &list = list_value.ValueList();
|
const auto &list = list_value.ValueList();
|
||||||
const auto &element_symbol = symbol_table_->at(*extract.identifier_);
|
const auto &element_symbol = symbol_table_->at(*extract.identifier_);
|
||||||
std::vector<TypedValue> result;
|
TypedValue::TVector result(ctx_->memory);
|
||||||
result.reserve(list.size());
|
result.reserve(list.size());
|
||||||
for (const auto &element : list) {
|
for (const auto &element : list) {
|
||||||
if (element.IsNull()) {
|
if (element.IsNull()) {
|
||||||
result.emplace_back(TypedValue());
|
result.emplace_back();
|
||||||
} else {
|
} else {
|
||||||
frame_->at(element_symbol) = element;
|
frame_->at(element_symbol) = element;
|
||||||
result.emplace_back(extract.expression_->Accept(*this));
|
result.emplace_back(extract.expression_->Accept(*this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TypedValue(result);
|
return TypedValue(result, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(All &all) override {
|
TypedValue Visit(All &all) override {
|
||||||
auto list_value = all.list_expression_->Accept(*this);
|
auto list_value = all.list_expression_->Accept(*this);
|
||||||
if (list_value.IsNull()) {
|
if (list_value.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
if (list_value.type() != TypedValue::Type::List) {
|
||||||
throw QueryRuntimeException("ALL expected a list, got {}.",
|
throw QueryRuntimeException("ALL expected a list, got {}.",
|
||||||
@ -440,13 +465,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TypedValue(true);
|
return TypedValue(true, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(Single &single) override {
|
TypedValue Visit(Single &single) override {
|
||||||
auto list_value = single.list_expression_->Accept(*this);
|
auto list_value = single.list_expression_->Accept(*this);
|
||||||
if (list_value.IsNull()) {
|
if (list_value.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
if (list_value.type() != TypedValue::Type::List) {
|
if (list_value.type() != TypedValue::Type::List) {
|
||||||
throw QueryRuntimeException("SINGLE expected a list, got {}.",
|
throw QueryRuntimeException("SINGLE expected a list, got {}.",
|
||||||
@ -468,24 +493,25 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
}
|
}
|
||||||
// Return false if more than one element satisfies the predicate.
|
// Return false if more than one element satisfies the predicate.
|
||||||
if (predicate_satisfied) {
|
if (predicate_satisfied) {
|
||||||
return TypedValue(false);
|
return TypedValue(false, ctx_->memory);
|
||||||
} else {
|
} else {
|
||||||
predicate_satisfied = true;
|
predicate_satisfied = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TypedValue(predicate_satisfied);
|
return TypedValue(predicate_satisfied, ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
||||||
return TypedValue(
|
return TypedValue(
|
||||||
ctx_->parameters.AtTokenPosition(param_lookup.token_position_));
|
ctx_->parameters.AtTokenPosition(param_lookup.token_position_),
|
||||||
|
ctx_->memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypedValue Visit(RegexMatch ®ex_match) override {
|
TypedValue Visit(RegexMatch ®ex_match) override {
|
||||||
auto target_string_value = regex_match.string_expr_->Accept(*this);
|
auto target_string_value = regex_match.string_expr_->Accept(*this);
|
||||||
auto regex_value = regex_match.regex_->Accept(*this);
|
auto regex_value = regex_match.regex_->Accept(*this);
|
||||||
if (target_string_value.IsNull() || regex_value.IsNull()) {
|
if (target_string_value.IsNull() || regex_value.IsNull()) {
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
if (regex_value.type() != TypedValue::Type::String) {
|
if (regex_value.type() != TypedValue::Type::String) {
|
||||||
throw QueryRuntimeException(
|
throw QueryRuntimeException(
|
||||||
@ -496,12 +522,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
|||||||
// Instead of error, we return Null which makes it compatible in case we
|
// Instead of error, we return Null which makes it compatible in case we
|
||||||
// use indexed lookup which filters out any non-string properties.
|
// use indexed lookup which filters out any non-string properties.
|
||||||
// Assuming a property lookup is the target_string_value.
|
// Assuming a property lookup is the target_string_value.
|
||||||
return TypedValue();
|
return TypedValue(ctx_->memory);
|
||||||
}
|
}
|
||||||
const auto &target_string = target_string_value.ValueString();
|
const auto &target_string = target_string_value.ValueString();
|
||||||
try {
|
try {
|
||||||
std::regex regex(regex_value.ValueString());
|
std::regex regex(regex_value.ValueString());
|
||||||
return TypedValue(std::regex_match(target_string, regex));
|
return TypedValue(std::regex_match(target_string, regex), ctx_->memory);
|
||||||
} catch (const std::regex_error &e) {
|
} catch (const std::regex_error &e) {
|
||||||
throw QueryRuntimeException("Regex error in '{}': {}",
|
throw QueryRuntimeException("Regex error in '{}': {}",
|
||||||
regex_value.ValueString(), e.what());
|
regex_value.ValueString(), e.what());
|
||||||
|
@ -127,6 +127,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, auth::Auth *auth,
|
|||||||
Frame frame(0);
|
Frame frame(0);
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
EvaluationContext evaluation_context;
|
EvaluationContext evaluation_context;
|
||||||
|
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||||
|
// the argument to Callback.
|
||||||
evaluation_context.timestamp =
|
evaluation_context.timestamp =
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
@ -421,6 +423,8 @@ Callback HandleStreamQuery(StreamQuery *stream_query,
|
|||||||
Frame frame(0);
|
Frame frame(0);
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
EvaluationContext evaluation_context;
|
EvaluationContext evaluation_context;
|
||||||
|
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||||
|
// the argument to Callback.
|
||||||
evaluation_context.timestamp =
|
evaluation_context.timestamp =
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
|
@ -105,6 +105,8 @@ class Interpreter {
|
|||||||
should_abort_query_(should_abort_query) {
|
should_abort_query_(should_abort_query) {
|
||||||
ctx_.is_profile_query = is_profile_query;
|
ctx_.is_profile_query = is_profile_query;
|
||||||
ctx_.symbol_table = plan_->symbol_table();
|
ctx_.symbol_table = plan_->symbol_table();
|
||||||
|
// TODO: Maybe we want a seperate MemoryResource per pull evaluation
|
||||||
|
ctx_.evaluation_context.memory = execution_memory_.get();
|
||||||
ctx_.evaluation_context.timestamp =
|
ctx_.evaluation_context.timestamp =
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
|
@ -33,6 +33,9 @@ target_link_libraries(${test_prefix}map_concurrent mg-single-node kvstore_dummy_
|
|||||||
add_benchmark(data_structures/ring_buffer.cpp)
|
add_benchmark(data_structures/ring_buffer.cpp)
|
||||||
target_link_libraries(${test_prefix}ring_buffer mg-single-node kvstore_dummy_lib)
|
target_link_libraries(${test_prefix}ring_buffer mg-single-node kvstore_dummy_lib)
|
||||||
|
|
||||||
|
add_benchmark(query/eval.cpp)
|
||||||
|
target_link_libraries(${test_prefix}eval mg-single-node kvstore_dummy_lib)
|
||||||
|
|
||||||
add_benchmark(query/execution.cpp)
|
add_benchmark(query/execution.cpp)
|
||||||
target_link_libraries(${test_prefix}execution mg-single-node kvstore_dummy_lib)
|
target_link_libraries(${test_prefix}execution mg-single-node kvstore_dummy_lib)
|
||||||
|
|
||||||
|
85
tests/benchmark/query/eval.cpp
Normal file
85
tests/benchmark/query/eval.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#include <benchmark/benchmark.h>
|
||||||
|
|
||||||
|
#include "query/interpret/eval.hpp"
|
||||||
|
|
||||||
|
// The following classes are wrappers for utils::MemoryResource, so that we can
|
||||||
|
// use BENCHMARK_TEMPLATE
|
||||||
|
|
||||||
|
class MonotonicBufferResource final {
|
||||||
|
utils::MonotonicBufferResource memory_{query::kExecutionMemoryBlockSize};
|
||||||
|
|
||||||
|
public:
|
||||||
|
utils::MemoryResource *get() { return &memory_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class NewDeleteResource final {
|
||||||
|
public:
|
||||||
|
utils::MemoryResource *get() { return utils::NewDeleteResource(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class TMemory>
|
||||||
|
// NOLINTNEXTLINE(google-runtime-references)
|
||||||
|
static void MapLiteral(benchmark::State &state) {
|
||||||
|
query::AstStorage ast;
|
||||||
|
query::SymbolTable symbol_table;
|
||||||
|
query::Frame frame(symbol_table.max_position());
|
||||||
|
TMemory memory;
|
||||||
|
database::GraphDb db;
|
||||||
|
auto dba = db.Access();
|
||||||
|
std::unordered_map<query::PropertyIx, query::Expression *> elements;
|
||||||
|
for (int64_t i = 0; i < state.range(0); ++i) {
|
||||||
|
elements.emplace(ast.GetPropertyIx("prop" + std::to_string(i)),
|
||||||
|
ast.Create<query::PrimitiveLiteral>(i));
|
||||||
|
}
|
||||||
|
auto *expr = ast.Create<query::MapLiteral>(elements);
|
||||||
|
query::EvaluationContext evaluation_context{memory.get()};
|
||||||
|
evaluation_context.properties =
|
||||||
|
query::NamesToProperties(ast.properties_, &dba);
|
||||||
|
query::ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context,
|
||||||
|
&dba, query::GraphView::NEW);
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
benchmark::DoNotOptimize(expr->Accept(evaluator));
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_TEMPLATE(MapLiteral, NewDeleteResource)
|
||||||
|
->Range(512, 1U << 15U)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_TEMPLATE(MapLiteral, MonotonicBufferResource)
|
||||||
|
->Range(512, 1U << 15U)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
template <class TMemory>
|
||||||
|
// NOLINTNEXTLINE(google-runtime-references)
|
||||||
|
static void AdditionOperator(benchmark::State &state) {
|
||||||
|
query::AstStorage ast;
|
||||||
|
query::SymbolTable symbol_table;
|
||||||
|
query::Frame frame(symbol_table.max_position());
|
||||||
|
TMemory memory;
|
||||||
|
database::GraphDb db;
|
||||||
|
auto dba = db.Access();
|
||||||
|
query::Expression *expr = ast.Create<query::PrimitiveLiteral>(0);
|
||||||
|
for (int64_t i = 0; i < state.range(0); ++i) {
|
||||||
|
expr = ast.Create<query::AdditionOperator>(
|
||||||
|
expr, ast.Create<query::PrimitiveLiteral>(i));
|
||||||
|
}
|
||||||
|
query::EvaluationContext evaluation_context{memory.get()};
|
||||||
|
query::ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context,
|
||||||
|
&dba, query::GraphView::NEW);
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
benchmark::DoNotOptimize(expr->Accept(evaluator));
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_TEMPLATE(AdditionOperator, NewDeleteResource)
|
||||||
|
->Range(1024, 1U << 15U)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_TEMPLATE(AdditionOperator, MonotonicBufferResource)
|
||||||
|
->Range(1024, 1U << 15U)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_MAIN();
|
@ -33,12 +33,12 @@ class ExpressionEvaluatorTest : public ::testing::Test {
|
|||||||
database::GraphDbAccessor dba{db.Access()};
|
database::GraphDbAccessor dba{db.Access()};
|
||||||
|
|
||||||
AstStorage storage;
|
AstStorage storage;
|
||||||
EvaluationContext ctx;
|
utils::MonotonicBufferResource mem{1024};
|
||||||
|
EvaluationContext ctx{&mem};
|
||||||
SymbolTable symbol_table;
|
SymbolTable symbol_table;
|
||||||
|
|
||||||
Frame frame{128};
|
Frame frame{128};
|
||||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba,
|
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, GraphView::OLD};
|
||||||
GraphView::OLD};
|
|
||||||
|
|
||||||
Identifier *CreateIdentifierWithValue(std::string name,
|
Identifier *CreateIdentifierWithValue(std::string name,
|
||||||
const TypedValue &value) {
|
const TypedValue &value) {
|
||||||
@ -53,7 +53,11 @@ class ExpressionEvaluatorTest : public ::testing::Test {
|
|||||||
auto Eval(TExpression *expr) {
|
auto Eval(TExpression *expr) {
|
||||||
ctx.properties = NamesToProperties(storage.properties_, &dba);
|
ctx.properties = NamesToProperties(storage.properties_, &dba);
|
||||||
ctx.labels = NamesToLabels(storage.labels_, &dba);
|
ctx.labels = NamesToLabels(storage.labels_, &dba);
|
||||||
return expr->Accept(eval);
|
auto value = expr->Accept(eval);
|
||||||
|
EXPECT_EQ(value.GetMemoryResource(), &mem)
|
||||||
|
<< "ExpressionEvaluator must use the MemoryResource from "
|
||||||
|
"EvaluationContext for allocations!";
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user