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: private:
std::string function_name_; std::string function_name_;
std::function<TypedValue(const std::vector<TypedValue> &, Context *)> std::function<TypedValue(TypedValue *, int64_t, Context *)> function_;
function_;
}; };
class Aggregation : public BinaryOperator { class Aggregation : public BinaryOperator {

View File

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

View File

@ -1,3 +1,4 @@
/// @file
#pragma once #pragma once
#include <vector> #include <vector>
@ -14,6 +15,14 @@ const char kEndsWith[] = "ENDSWITH";
const char kContains[] = "CONTAINS"; const char kContains[] = "CONTAINS";
} // namespace } // 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); NameToFunction(const std::string &function_name);
} // namespace query } // namespace query

View File

@ -8,6 +8,7 @@
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "query/common.hpp" #include "query/common.hpp"
#include "query/context.hpp"
#include "query/exceptions.hpp" #include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp" #include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.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 // 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 {}.", throw QueryRuntimeException("IN expected a list, got {}.", _list.type());
_list.type());
} }
auto list = _list.Value<std::vector<TypedValue>>(); auto list = _list.Value<std::vector<TypedValue>>();
@ -366,11 +366,22 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
} }
TypedValue Visit(Function &function) override { 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; std::vector<TypedValue> arguments;
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, context_); return function.function()(arguments.data(), arguments.size(), context_);
}
} }
TypedValue Visit(Reduce &reduce) override { TypedValue Visit(Reduce &reduce) override {

View File

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