Use stack allocation for cypher function arguments

Summary:
This is a simple change which modifies interface of
awesome_memgraph_functions to accept C-style pointer to array with
count. Doing things this way, allows us to easily try out different
allocation schemes for function arguments. In this diff, we are now
using stack allocation of arguments in a plain fixed size array. This is
done when the number of arguments is small. According to heaptrack, this
small change should yield noticeable improvements to heap usage.

Obviously, this doesn't solve the problem of heap allocations inside
TypedValue arguments themselves. These allocations appear when
std::string and std::vector is used inside TypedValue.

Micro benchmarks show that there is some performance improvement,
mostly around the limits of using array vs std::vector. The improvement is
more noticeable with multiple threads, due to primary gain being in avoiding
calls to memory allocation.

Reviewers: mtomic, msantl, mferencevic

Reviewed By: mferencevic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1581
This commit is contained in:
Teon Banek 2018-08-31 15:13:18 +02:00
parent ee889b98fc
commit 88de3422d0
6 changed files with 177 additions and 100 deletions

View File

@ -1013,8 +1013,7 @@ class Function : public Expression {
private:
std::string function_name_;
std::function<TypedValue(const std::vector<TypedValue> &, Context *)>
function_;
std::function<TypedValue(TypedValue *, int64_t, Context *)> function_;
};
class Aggregation : public BinaryOperator {

View File

@ -32,20 +32,22 @@ namespace {
// TODO: Implement degrees, haversin, radians
// TODO: Implement spatial functions
TypedValue Coalesce(const std::vector<TypedValue> &args, Context *) {
if (args.size() == 0U) {
TypedValue Coalesce(TypedValue *args, int64_t nargs, Context *) {
// TODO: Perhaps this function should be done by the evaluator itself, so as
// to avoid evaluating all the arguments.
if (nargs == 0) {
throw QueryRuntimeException("'coalesce' requires at least one argument.");
}
for (auto &arg : args) {
if (arg.type() != TypedValue::Type::Null) {
return arg;
for (int64_t i = 0; i < nargs; ++i) {
if (args[i].type() != TypedValue::Type::Null) {
return args[i];
}
}
return TypedValue::Null;
}
TypedValue EndNode(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue EndNode(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'endNode' requires exactly one argument.");
}
switch (args[0].type()) {
@ -58,8 +60,8 @@ TypedValue EndNode(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Head(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Head(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'head' requires exactly one argument.");
}
switch (args[0].type()) {
@ -75,8 +77,8 @@ TypedValue Head(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Last(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Last(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'last' requires exactly one argument.");
}
switch (args[0].type()) {
@ -92,8 +94,8 @@ TypedValue Last(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Properties(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Properties(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'properties' requires exactly one argument.");
}
auto get_properties = [&](const auto &record_accessor) {
@ -117,8 +119,8 @@ TypedValue Properties(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue Size(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Size(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'size' requires exactly one argument.");
}
switch (args[0].type()) {
@ -142,8 +144,8 @@ TypedValue Size(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue StartNode(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue StartNode(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'startNode' requires exactly one argument.");
}
switch (args[0].type()) {
@ -156,8 +158,8 @@ TypedValue StartNode(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Degree(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Degree(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'degree' requires exactly one argument.");
}
switch (args[0].type()) {
@ -172,8 +174,8 @@ TypedValue Degree(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue ToBoolean(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue ToBoolean(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'toBoolean' requires exactly one argument.");
}
switch (args[0].type()) {
@ -197,8 +199,8 @@ TypedValue ToBoolean(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue ToFloat(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue ToFloat(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'toFloat' requires exactly one argument.");
}
switch (args[0].type()) {
@ -220,8 +222,8 @@ TypedValue ToFloat(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue ToInteger(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue ToInteger(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'toInteger' requires exactly one argument'");
}
switch (args[0].type()) {
@ -248,8 +250,8 @@ TypedValue ToInteger(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Type(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Type(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'type' requires exactly one argument.");
}
switch (args[0].type()) {
@ -263,8 +265,8 @@ TypedValue Type(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue Keys(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Keys(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'keys' requires exactly one argument.");
}
auto get_keys = [&](const auto &record_accessor) {
@ -286,8 +288,8 @@ TypedValue Keys(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue Labels(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Labels(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'labels' requires exactly one argument.");
}
switch (args[0].type()) {
@ -305,8 +307,8 @@ TypedValue Labels(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue Nodes(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Nodes(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'nodes' requires exactly one argument.");
}
if (args[0].IsNull()) return TypedValue::Null;
@ -317,8 +319,8 @@ TypedValue Nodes(const std::vector<TypedValue> &args, Context *) {
return std::vector<TypedValue>(vertices.begin(), vertices.end());
}
TypedValue Relationships(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Relationships(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException(
"'relationships' requires exactly one argument.");
}
@ -330,8 +332,8 @@ TypedValue Relationships(const std::vector<TypedValue> &args, Context *) {
return std::vector<TypedValue>(edges.begin(), edges.end());
}
TypedValue Range(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U && args.size() != 3U) {
TypedValue Range(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 2 && nargs != 3) {
throw QueryRuntimeException("'range' requires two or three arguments.");
}
bool has_null = false;
@ -342,11 +344,11 @@ TypedValue Range(const std::vector<TypedValue> &args, Context *) {
throw QueryRuntimeException("arguments of 'range' must be integers.");
}
};
std::for_each(args.begin(), args.end(), check_type);
for (int64_t i = 0; i < nargs; ++i) check_type(args[i]);
if (has_null) return TypedValue::Null;
auto lbound = args[0].Value<int64_t>();
auto rbound = args[1].Value<int64_t>();
int64_t step = args.size() == 3U ? args[2].Value<int64_t>() : 1;
int64_t step = nargs == 3 ? args[2].Value<int64_t>() : 1;
if (step == 0) {
throw QueryRuntimeException("step argument of 'range' can't be zero.");
}
@ -363,8 +365,8 @@ TypedValue Range(const std::vector<TypedValue> &args, Context *) {
return list;
}
TypedValue Tail(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Tail(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'tail' requires exactly one argument.");
}
switch (args[0].type()) {
@ -381,8 +383,8 @@ TypedValue Tail(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Abs(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Abs(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'abs' requires exactly one argument.");
}
switch (args[0].type()) {
@ -399,8 +401,8 @@ TypedValue Abs(const std::vector<TypedValue> &args, Context *) {
}
#define WRAP_CMATH_FLOAT_FUNCTION(name, lowercased_name) \
TypedValue name(const std::vector<TypedValue> &args, Context *) { \
if (args.size() != 1U) { \
TypedValue name(TypedValue *args, int64_t nargs, Context *) { \
if (nargs != 1) { \
throw QueryRuntimeException("'" #lowercased_name \
"' requires exactly one argument."); \
} \
@ -435,8 +437,8 @@ WRAP_CMATH_FLOAT_FUNCTION(Tan, tan)
#undef WRAP_CMATH_FLOAT_FUNCTION
TypedValue Atan2(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U) {
TypedValue Atan2(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 2) {
throw QueryRuntimeException("'atan2' requires two arguments.");
}
if (args[0].type() == TypedValue::Type::Null) return TypedValue::Null;
@ -456,8 +458,8 @@ TypedValue Atan2(const std::vector<TypedValue> &args, Context *) {
return atan2(y, x);
}
TypedValue Sign(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue Sign(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'sign' requires exactly one argument.");
}
auto sign = [](auto x) { return (0 < x) - (x < 0); };
@ -473,32 +475,32 @@ TypedValue Sign(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue E(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 0U) {
TypedValue E(TypedValue *, int64_t nargs, Context *) {
if (nargs != 0) {
throw QueryRuntimeException("'e' requires no arguments.");
}
return M_E;
}
TypedValue Pi(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 0U) {
TypedValue Pi(TypedValue *, int64_t nargs, Context *) {
if (nargs != 0) {
throw QueryRuntimeException("'pi' requires no arguments.");
}
return M_PI;
}
TypedValue Rand(const std::vector<TypedValue> &args, Context *) {
TypedValue Rand(TypedValue *, int64_t nargs, Context *) {
static thread_local std::mt19937 pseudo_rand_gen_{std::random_device{}()};
static thread_local std::uniform_real_distribution<> rand_dist_{0, 1};
if (args.size() != 0U) {
if (nargs != 0) {
throw QueryRuntimeException("'rand' requires no arguments.");
}
return rand_dist_(pseudo_rand_gen_);
}
template <bool (*Predicate)(const std::string &s1, const std::string &s2)>
TypedValue StringMatchOperator(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U) {
TypedValue StringMatchOperator(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 2) {
throw QueryRuntimeException(
"'startsWith' and 'endsWith' require two arguments.");
}
@ -540,27 +542,27 @@ bool ContainsPredicate(const std::string &s1, const std::string &s2) {
}
auto Contains = StringMatchOperator<ContainsPredicate>;
TypedValue Assert(const std::vector<TypedValue> &args, Context *) {
if (args.size() < 1U || args.size() > 2U) {
TypedValue Assert(TypedValue *args, int64_t nargs, Context *) {
if (nargs < 1 || nargs > 2) {
throw QueryRuntimeException("'assert' requires one or two arguments");
}
if (args[0].type() != TypedValue::Type::Bool)
throw QueryRuntimeException(
"First argument of 'assert' must be a boolean.");
if (args.size() == 2U && args[1].type() != TypedValue::Type::String)
if (nargs == 2 && args[1].type() != TypedValue::Type::String)
throw QueryRuntimeException(
"Second argument of 'assert' must be a string.");
if (!args[0].ValueBool()) {
std::string message("Assertion failed");
if (args.size() == 2U) message += ": " + args[1].ValueString();
if (nargs == 2) message += ": " + args[1].ValueString();
message += ".";
throw QueryRuntimeException(message);
}
return args[0];
}
TypedValue Counter(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Counter(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'counter' requires exactly one argument.");
}
if (!args[0].IsString())
@ -569,8 +571,8 @@ TypedValue Counter(const std::vector<TypedValue> &args, Context *ctx) {
return ctx->db_accessor_.Counter(args[0].ValueString());
}
TypedValue CounterSet(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2U) {
TypedValue CounterSet(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 2) {
throw QueryRuntimeException("'counterSet' requires two arguments.");
}
if (!args[0].IsString())
@ -583,16 +585,16 @@ TypedValue CounterSet(const std::vector<TypedValue> &args, Context *ctx) {
return TypedValue::Null;
}
TypedValue IndexInfo(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 0U)
TypedValue IndexInfo(TypedValue *, int64_t nargs, Context *ctx) {
if (nargs != 0)
throw QueryRuntimeException("'indexInfo' requires no arguments.");
auto info = ctx->db_accessor_.IndexInfo();
return std::vector<TypedValue>(info.begin(), info.end());
}
TypedValue WorkerId(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue WorkerId(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'workerId' requires exactly one argument.");
}
auto &arg = args[0];
@ -607,8 +609,8 @@ TypedValue WorkerId(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Id(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 1U) {
TypedValue Id(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 1) {
throw QueryRuntimeException("'id' requires exactly one argument.");
}
auto &arg = args[0];
@ -624,8 +626,8 @@ TypedValue Id(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue ToString(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 1U) {
TypedValue ToString(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 1) {
throw QueryRuntimeException("'toString' requires exactly one argument.");
}
auto &arg = args[0];
@ -646,15 +648,15 @@ TypedValue ToString(const std::vector<TypedValue> &args, Context *) {
}
}
TypedValue Timestamp(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 0) {
TypedValue Timestamp(TypedValue *, int64_t nargs, Context *ctx) {
if (nargs != 0) {
throw QueryRuntimeException("'timestamp' requires no arguments.");
}
return ctx->timestamp_;
}
TypedValue Left(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2) {
TypedValue Left(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 2) {
throw QueryRuntimeException("'left' requires two arguments.");
}
switch (args[0].type()) {
@ -675,8 +677,8 @@ TypedValue Left(const std::vector<TypedValue> &args, Context *ctx) {
}
}
TypedValue Right(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2) {
TypedValue Right(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 2) {
throw QueryRuntimeException("'right' requires two arguments.");
}
switch (args[0].type()) {
@ -702,8 +704,8 @@ TypedValue Right(const std::vector<TypedValue> &args, Context *ctx) {
}
#define WRAP_STRING_FUNCTION(name, lowercased_name, function) \
TypedValue name(const std::vector<TypedValue> &args, Context *) { \
if (args.size() != 1U) { \
TypedValue name(TypedValue *args, int64_t nargs, Context *) { \
if (nargs != 1) { \
throw QueryRuntimeException("'" #lowercased_name \
"' requires exactly one argument."); \
} \
@ -725,8 +727,8 @@ WRAP_STRING_FUNCTION(Reverse, reverse, utils::Reversed);
WRAP_STRING_FUNCTION(ToLower, toLower, utils::ToLowerCase);
WRAP_STRING_FUNCTION(ToUpper, toUpper, utils::ToUpperCase);
TypedValue Replace(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 3U) {
TypedValue Replace(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 3) {
throw QueryRuntimeException("'replace' requires three arguments.");
}
if (!args[0].IsNull() && !args[0].IsString()) {
@ -748,8 +750,8 @@ TypedValue Replace(const std::vector<TypedValue> &args, Context *ctx) {
args[2].ValueString());
}
TypedValue Split(const std::vector<TypedValue> &args, Context *ctx) {
if (args.size() != 2U) {
TypedValue Split(TypedValue *args, int64_t nargs, Context *ctx) {
if (nargs != 2) {
throw QueryRuntimeException("'split' requires two arguments.");
}
if (!args[0].IsNull() && !args[0].IsString()) {
@ -771,8 +773,8 @@ TypedValue Split(const std::vector<TypedValue> &args, Context *ctx) {
return result;
}
TypedValue Substring(const std::vector<TypedValue> &args, Context *) {
if (args.size() != 2U && args.size() != 3U) {
TypedValue Substring(TypedValue *args, int64_t nargs, Context *) {
if (nargs != 2 && nargs != 3) {
throw QueryRuntimeException("'substring' requires two or three arguments.");
}
if (!args[0].IsNull() && !args[0].IsString()) {
@ -783,7 +785,7 @@ TypedValue Substring(const std::vector<TypedValue> &args, Context *) {
throw QueryRuntimeException(
"Second argument of 'substring' should be a non-negative integer.");
}
if (args.size() == 3U && (!args[2].IsInt() || args[2].ValueInt() < 0)) {
if (nargs == 3 && (!args[2].IsInt() || args[2].ValueInt() < 0)) {
throw QueryRuntimeException(
"Third argument of 'substring' should be a non-negative integer.");
}
@ -792,7 +794,7 @@ TypedValue Substring(const std::vector<TypedValue> &args, Context *) {
}
const auto &str = args[0].ValueString();
int start = args[1].ValueInt();
if (args.size() == 2U) {
if (nargs == 2) {
return start < str.size() ? str.substr(start) : "";
}
int len = args[2].ValueInt();
@ -801,7 +803,7 @@ TypedValue Substring(const std::vector<TypedValue> &args, Context *) {
} // namespace
std::function<TypedValue(const std::vector<TypedValue> &, Context *)>
std::function<TypedValue(TypedValue *, int64_t, Context *)>
NameToFunction(const std::string &function_name) {
// Scalar functions
if (function_name == "COALESCE") return Coalesce;
@ -878,4 +880,5 @@ NameToFunction(const std::string &function_name) {
return nullptr;
}
} // namespace query

View File

@ -1,3 +1,4 @@
/// @file
#pragma once
#include <vector>
@ -14,6 +15,14 @@ const char kEndsWith[] = "ENDSWITH";
const char kContains[] = "CONTAINS";
} // namespace
std::function<TypedValue(const std::vector<TypedValue> &, Context *)>
/// Return the function implementation with the given name.
///
/// Note, returned function signature uses C-style access to an array to allow
/// having an array stored anywhere the caller likes, as long as it is
/// contiguous in memory. Since most functions don't take many arguments, it's
/// convenient to have them stored in the calling stack frame.
std::function<TypedValue(TypedValue *arguments, int64_t num_arguments,
Context *context)>
NameToFunction(const std::string &function_name);
} // namespace query

View File

@ -8,6 +8,7 @@
#include "database/graph_db_accessor.hpp"
#include "query/common.hpp"
#include "query/context.hpp"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
@ -157,8 +158,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
// 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());
throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
}
auto list = _list.Value<std::vector<TypedValue>>();
@ -366,11 +366,22 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
}
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];
for (size_t i = 0; i < function.arguments_.size(); ++i) {
arguments[i] = function.arguments_[i]->Accept(*this);
}
return function.function()(arguments, function.arguments_.size(),
context_);
} else {
std::vector<TypedValue> arguments;
arguments.reserve(function.arguments_.size());
for (const auto &argument : function.arguments_) {
arguments.emplace_back(argument->Accept(*this));
}
return function.function()(arguments, context_);
return function.function()(arguments.data(), arguments.size(), context_);
}
}
TypedValue Visit(Reduce &reduce) override {

View File

@ -9,7 +9,7 @@ namespace query {
class Frame {
public:
Frame(int size) : size_(size), elems_(size_) {}
explicit Frame(int size) : size_(size), elems_(size_) {}
TypedValue &operator[](const Symbol &symbol) {
return elems_[symbol.position()];

View File

@ -0,0 +1,55 @@
#include <benchmark/benchmark.h>
#include "query/frontend/ast/ast.hpp"
#include "query/interpret/eval.hpp"
static void BenchmarkCoalesceCallWithNulls(benchmark::State &state) {
int64_t num_args = state.range(0);
query::AstStorage ast_storage;
std::vector<query::Expression *> arguments;
arguments.reserve(num_args);
for (int64_t i = 0; i < num_args; ++i) {
arguments.emplace_back(
ast_storage.Create<query::PrimitiveLiteral>(query::TypedValue::Null));
}
auto *function = ast_storage.Create<query::Function>("COALESCE", arguments);
query::Frame frame(0);
database::GraphDbAccessor *dba = nullptr;
query::Context context(*dba);
query::ExpressionEvaluator evaluator(frame, &context, query::GraphView::OLD);
while (state.KeepRunning()) {
function->Accept(evaluator);
}
}
static void BenchmarkCoalesceCallWithStrings(benchmark::State &state) {
int64_t num_args = state.range(0);
query::AstStorage ast_storage;
std::vector<query::Expression *> arguments;
arguments.reserve(num_args);
for (int64_t i = 0; i < num_args; ++i) {
std::string val = "some_string " + std::to_string(i);
arguments.emplace_back(ast_storage.Create<query::PrimitiveLiteral>(val));
}
auto *function = ast_storage.Create<query::Function>("COALESCE", arguments);
query::Frame frame(0);
database::GraphDbAccessor *dba = nullptr;
query::Context context(*dba);
query::ExpressionEvaluator evaluator(frame, &context, query::GraphView::OLD);
while (state.KeepRunning()) {
function->Accept(evaluator);
}
}
// We are interested in benchmarking the usual amount of arguments
BENCHMARK(BenchmarkCoalesceCallWithNulls)
->RangeMultiplier(2)
->Range(1, 256)
->ThreadRange(1, 16);
BENCHMARK(BenchmarkCoalesceCallWithStrings)
->RangeMultiplier(2)
->Range(1, 256)
->ThreadRange(1, 16);
BENCHMARK_MAIN();