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)),
|
||||
cursor_(plan_pack.plan->MakeCursor(dba_.get(), execution_memory_.get())) {
|
||||
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.parameters = parameters;
|
||||
context_.evaluation_context.properties =
|
||||
|
@ -10,6 +10,12 @@ namespace query {
|
||||
static constexpr size_t kExecutionMemoryBlockSize = 1U * 1024U * 1024U;
|
||||
|
||||
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};
|
||||
Parameters parameters;
|
||||
/// All properties indexable via PropertyIx
|
||||
|
@ -32,6 +32,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
|
||||
using ExpressionVisitor<TypedValue>::Visit;
|
||||
|
||||
utils::MemoryResource *GetMemoryResource() const { return ctx_->memory; }
|
||||
|
||||
TypedValue Visit(NamedExpression &named_expression) override {
|
||||
const auto &symbol = symbol_table_->at(named_expression);
|
||||
auto value = named_expression.expression_->Accept(*this);
|
||||
@ -40,7 +42,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
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);
|
||||
return value;
|
||||
}
|
||||
@ -124,21 +126,21 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
auto literal = in_list.expression1_->Accept(*this);
|
||||
auto _list = in_list.expression2_->Accept(*this);
|
||||
if (_list.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
// Exceptions have higher priority than returning nulls when list expression
|
||||
// is not null.
|
||||
if (_list.type() != TypedValue::Type::List) {
|
||||
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
|
||||
// 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
|
||||
// result is false since no comparison will be performed.
|
||||
if (list.empty()) return TypedValue(false);
|
||||
if (literal.IsNull()) return TypedValue();
|
||||
if (list.empty()) return TypedValue(false, ctx_->memory);
|
||||
if (literal.IsNull()) return TypedValue(ctx_->memory);
|
||||
|
||||
auto has_null = false;
|
||||
for (const auto &element : list) {
|
||||
@ -146,13 +148,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
if (result.IsNull()) {
|
||||
has_null = true;
|
||||
} else if (result.Value<bool>()) {
|
||||
return TypedValue(true);
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
}
|
||||
if (has_null) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
return TypedValue(false);
|
||||
return TypedValue(false, ctx_->memory);
|
||||
}
|
||||
|
||||
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 "
|
||||
"{}.",
|
||||
lhs.type());
|
||||
if (lhs.IsNull() || index.IsNull()) return TypedValue();
|
||||
if (lhs.IsNull() || index.IsNull()) return TypedValue(ctx_->memory);
|
||||
if (lhs.IsList()) {
|
||||
if (!index.IsInt())
|
||||
throw QueryRuntimeException(
|
||||
"Expected an integer as a list index, got {}.", index.type());
|
||||
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) {
|
||||
index_int += static_cast<int64_t>(list.size());
|
||||
}
|
||||
if (index_int >= static_cast<int64_t>(list.size()) || index_int < 0)
|
||||
return TypedValue();
|
||||
return list[index_int];
|
||||
return TypedValue(ctx_->memory);
|
||||
// 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 (!index.IsString())
|
||||
throw QueryRuntimeException("Expected a string as a map index, got {}.",
|
||||
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());
|
||||
if (found == map.end()) return TypedValue();
|
||||
return found->second;
|
||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||
// 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()) {
|
||||
@ -194,7 +204,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
throw QueryRuntimeException(
|
||||
"Expected a string as a property name, got {}.", index.type());
|
||||
return TypedValue(lhs.Value<VertexAccessor>().PropsAt(
|
||||
dba_->Property(std::string(index.ValueString()))));
|
||||
dba_->Property(std::string(index.ValueString()))),
|
||||
ctx_->memory);
|
||||
}
|
||||
|
||||
if (lhs.IsEdge()) {
|
||||
@ -202,11 +213,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
throw QueryRuntimeException(
|
||||
"Expected a string as a property name, got {}.", index.type());
|
||||
return TypedValue(lhs.Value<EdgeAccessor>().PropsAt(
|
||||
dba_->Property(std::string(index.ValueString()))));
|
||||
dba_->Property(std::string(index.ValueString()))),
|
||||
ctx_->memory);
|
||||
}
|
||||
|
||||
// lhs is Null
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(ListSlicingOperator &op) override {
|
||||
@ -225,7 +237,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
return bound;
|
||||
}
|
||||
return TypedValue(default_value);
|
||||
return TypedValue(default_value, ctx_->memory);
|
||||
};
|
||||
auto _upper_bound =
|
||||
get_bound(op.upper_bound_, std::numeric_limits<int64_t>::max());
|
||||
@ -240,7 +252,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
if (is_null) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
const auto &list = _list.ValueList();
|
||||
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 upper_bound = normalise_bound(_upper_bound.Value<int64_t>());
|
||||
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,
|
||||
list.begin() + upper_bound));
|
||||
return TypedValue(TypedValue::TVector(
|
||||
list.begin() + lower_bound, list.begin() + upper_bound, ctx_->memory));
|
||||
}
|
||||
|
||||
TypedValue Visit(IsNullOperator &is_null) override {
|
||||
auto value = is_null.expression_->Accept(*this);
|
||||
return TypedValue(value.IsNull());
|
||||
return TypedValue(value.IsNull(), ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(PropertyLookup &property_lookup) override {
|
||||
auto expression_result = property_lookup.expression_->Accept(*this);
|
||||
switch (expression_result.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex:
|
||||
return TypedValue(expression_result.Value<VertexAccessor>().PropsAt(
|
||||
GetProperty(property_lookup.property_)));
|
||||
GetProperty(property_lookup.property_)),
|
||||
ctx_->memory);
|
||||
case TypedValue::Type::Edge:
|
||||
return TypedValue(expression_result.Value<EdgeAccessor>().PropsAt(
|
||||
GetProperty(property_lookup.property_)));
|
||||
GetProperty(property_lookup.property_)),
|
||||
ctx_->memory);
|
||||
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());
|
||||
if (found == map.end()) return TypedValue();
|
||||
return found->second;
|
||||
if (found == map.end()) return TypedValue(ctx_->memory);
|
||||
// NOTE: Explicit move is needed, so that we return the move constructed
|
||||
// value and preserve the correct MemoryResource.
|
||||
return std::move(found->second);
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException(
|
||||
@ -291,15 +309,15 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
auto expression_result = labels_test.expression_->Accept(*this);
|
||||
switch (expression_result.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
case TypedValue::Type::Vertex: {
|
||||
auto vertex = expression_result.Value<VertexAccessor>();
|
||||
for (const auto label : labels_test.labels_) {
|
||||
const auto &vertex = expression_result.Value<VertexAccessor>();
|
||||
for (const auto &label : labels_test.labels_) {
|
||||
if (!vertex.has_label(GetLabel(label))) {
|
||||
return TypedValue(false);
|
||||
return TypedValue(false, ctx_->memory);
|
||||
}
|
||||
}
|
||||
return TypedValue(true);
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
default:
|
||||
throw QueryRuntimeException("Only nodes have labels.");
|
||||
@ -309,26 +327,26 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
TypedValue Visit(PrimitiveLiteral &literal) override {
|
||||
// TODO: no need to evaluate constants, we can write it to frame in one
|
||||
// of the previous phases.
|
||||
return TypedValue(literal.value_);
|
||||
return TypedValue(literal.value_, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(ListLiteral &literal) override {
|
||||
std::vector<TypedValue> result;
|
||||
TypedValue::TVector result(ctx_->memory);
|
||||
result.reserve(literal.elements_.size());
|
||||
for (const auto &expression : literal.elements_)
|
||||
result.emplace_back(expression->Accept(*this));
|
||||
return TypedValue(result);
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(MapLiteral &literal) override {
|
||||
std::map<std::string, TypedValue> result;
|
||||
TypedValue::TMap result(ctx_->memory);
|
||||
for (const auto &pair : literal.elements_)
|
||||
result.emplace(pair.first.name, pair.second->Accept(*this));
|
||||
return TypedValue(result);
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
|
||||
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
|
||||
// just to be sure.
|
||||
SwitchAccessors(value);
|
||||
@ -343,39 +361,46 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
|
||||
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()) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(Function &function) override {
|
||||
// Stack allocate evaluated arguments when there's a small number of them.
|
||||
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) {
|
||||
arguments[i] = function.arguments_[i]->Accept(*this);
|
||||
}
|
||||
return function.function_(arguments, function.arguments_.size(), *ctx_,
|
||||
dba_);
|
||||
// TODO: Update awesome_memgraph_functions to use the allocator from ctx_
|
||||
return TypedValue(function.function_(
|
||||
arguments, function.arguments_.size(), *ctx_, dba_),
|
||||
ctx_->memory);
|
||||
} else {
|
||||
std::vector<TypedValue> arguments;
|
||||
TypedValue::TVector arguments(ctx_->memory);
|
||||
arguments.reserve(function.arguments_.size());
|
||||
for (const auto &argument : function.arguments_) {
|
||||
arguments.emplace_back(argument->Accept(*this));
|
||||
}
|
||||
return function.function_(arguments.data(), arguments.size(), *ctx_,
|
||||
dba_);
|
||||
// TODO: Update awesome_memgraph_functions to use the allocator from ctx_
|
||||
return TypedValue(
|
||||
function.function_(arguments.data(), arguments.size(), *ctx_, dba_),
|
||||
ctx_->memory);
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue Visit(Reduce &reduce) override {
|
||||
auto list_value = reduce.list_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw QueryRuntimeException("REDUCE expected a list, got {}.",
|
||||
@ -396,7 +421,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
TypedValue Visit(Extract &extract) override {
|
||||
auto list_value = extract.list_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw QueryRuntimeException("EXTRACT expected a list, got {}.",
|
||||
@ -404,23 +429,23 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
}
|
||||
const auto &list = list_value.ValueList();
|
||||
const auto &element_symbol = symbol_table_->at(*extract.identifier_);
|
||||
std::vector<TypedValue> result;
|
||||
TypedValue::TVector result(ctx_->memory);
|
||||
result.reserve(list.size());
|
||||
for (const auto &element : list) {
|
||||
if (element.IsNull()) {
|
||||
result.emplace_back(TypedValue());
|
||||
result.emplace_back();
|
||||
} else {
|
||||
frame_->at(element_symbol) = element;
|
||||
result.emplace_back(extract.expression_->Accept(*this));
|
||||
}
|
||||
}
|
||||
return TypedValue(result);
|
||||
return TypedValue(result, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(All &all) override {
|
||||
auto list_value = all.list_expression_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
throw QueryRuntimeException("ALL expected a list, got {}.",
|
||||
@ -440,13 +465,13 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return TypedValue(true);
|
||||
return TypedValue(true, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(Single &single) override {
|
||||
auto list_value = single.list_expression_->Accept(*this);
|
||||
if (list_value.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (list_value.type() != TypedValue::Type::List) {
|
||||
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.
|
||||
if (predicate_satisfied) {
|
||||
return TypedValue(false);
|
||||
return TypedValue(false, ctx_->memory);
|
||||
} else {
|
||||
predicate_satisfied = true;
|
||||
}
|
||||
}
|
||||
return TypedValue(predicate_satisfied);
|
||||
return TypedValue(predicate_satisfied, ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(ParameterLookup ¶m_lookup) override {
|
||||
return TypedValue(
|
||||
ctx_->parameters.AtTokenPosition(param_lookup.token_position_));
|
||||
ctx_->parameters.AtTokenPosition(param_lookup.token_position_),
|
||||
ctx_->memory);
|
||||
}
|
||||
|
||||
TypedValue Visit(RegexMatch ®ex_match) override {
|
||||
auto target_string_value = regex_match.string_expr_->Accept(*this);
|
||||
auto regex_value = regex_match.regex_->Accept(*this);
|
||||
if (target_string_value.IsNull() || regex_value.IsNull()) {
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
if (regex_value.type() != TypedValue::Type::String) {
|
||||
throw QueryRuntimeException(
|
||||
@ -496,12 +522,12 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
|
||||
// Instead of error, we return Null which makes it compatible in case we
|
||||
// use indexed lookup which filters out any non-string properties.
|
||||
// Assuming a property lookup is the target_string_value.
|
||||
return TypedValue();
|
||||
return TypedValue(ctx_->memory);
|
||||
}
|
||||
const auto &target_string = target_string_value.ValueString();
|
||||
try {
|
||||
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) {
|
||||
throw QueryRuntimeException("Regex error in '{}': {}",
|
||||
regex_value.ValueString(), e.what());
|
||||
|
@ -127,6 +127,8 @@ Callback HandleAuthQuery(AuthQuery *auth_query, auth::Auth *auth,
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
@ -421,6 +423,8 @@ Callback HandleStreamQuery(StreamQuery *stream_query,
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
|
@ -105,6 +105,8 @@ class Interpreter {
|
||||
should_abort_query_(should_abort_query) {
|
||||
ctx_.is_profile_query = is_profile_query;
|
||||
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 =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
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)
|
||||
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)
|
||||
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()};
|
||||
|
||||
AstStorage storage;
|
||||
EvaluationContext ctx;
|
||||
utils::MonotonicBufferResource mem{1024};
|
||||
EvaluationContext ctx{&mem};
|
||||
SymbolTable symbol_table;
|
||||
|
||||
Frame frame{128};
|
||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba,
|
||||
GraphView::OLD};
|
||||
ExpressionEvaluator eval{&frame, symbol_table, ctx, &dba, GraphView::OLD};
|
||||
|
||||
Identifier *CreateIdentifierWithValue(std::string name,
|
||||
const TypedValue &value) {
|
||||
@ -53,7 +53,11 @@ class ExpressionEvaluatorTest : public ::testing::Test {
|
||||
auto Eval(TExpression *expr) {
|
||||
ctx.properties = NamesToProperties(storage.properties_, &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