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:
Teon Banek 2019-06-07 14:31:25 +02:00
parent 57d967786c
commit 3fd14e2d5f
8 changed files with 200 additions and 68 deletions

View File

@ -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 =

View File

@ -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

View File

@ -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 &param_lookup) override { TypedValue Visit(ParameterLookup &param_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 &regex_match) override { TypedValue Visit(RegexMatch &regex_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());

View File

@ -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())

View File

@ -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())

View File

@ -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)

View 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();

View File

@ -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;
} }
}; };