summaryrefslogtreecommitdiffstats
path: root/eval
diff options
context:
space:
mode:
authorHåvard Pettersen <havardpe@oath.com>2020-06-12 13:30:49 +0000
committerHåvard Pettersen <havardpe@oath.com>2020-06-12 13:30:49 +0000
commit38d3d076ebee23e014858daad173c72f142d8ac5 (patch)
treec7b283f79d0fa4fd83a88b8f04de7789a97e8f15 /eval
parent48d87b3033b712afeb54b888d582077671f3ad51 (diff)
inline more operations
Diffstat (limited to 'eval')
-rw-r--r--eval/src/tests/eval/inline_operation/inline_operation_test.cpp214
-rw-r--r--eval/src/vespa/eval/eval/inline_operation.h50
-rw-r--r--eval/src/vespa/eval/eval/operation.cpp4
-rw-r--r--eval/src/vespa/eval/eval/operation.h2
4 files changed, 241 insertions, 29 deletions
diff --git a/eval/src/tests/eval/inline_operation/inline_operation_test.cpp b/eval/src/tests/eval/inline_operation/inline_operation_test.cpp
index bfcb3a09a52..2aa159f46d7 100644
--- a/eval/src/tests/eval/inline_operation/inline_operation_test.cpp
+++ b/eval/src/tests/eval/inline_operation/inline_operation_test.cpp
@@ -3,27 +3,29 @@
#include <vespa/eval/eval/operation.h>
#include <vespa/eval/eval/inline_operation.h>
#include <vespa/eval/eval/function.h>
+#include <vespa/vespalib/util/typify.h>
#include <vespa/vespalib/gtest/gtest.h>
+using vespalib::typify_invoke;
using namespace vespalib::eval;
using namespace vespalib::eval::operation;
-template <typename T> struct IsInlined { constexpr static bool value = true; };
-template <> struct IsInlined<CallOp1> { constexpr static bool value = false; };
-template <> struct IsInlined<CallOp2> { constexpr static bool value = false; };
+const int my_value = 42;
+struct AsValue { template <typename T> static int invoke() { return my_value; } };
+struct AsRef { template <typename T> static const int &invoke() { return my_value; } };
-template <typename T> double test_op1(op1_t ref, double a, bool inlined) {
- T op(ref);
- EXPECT_EQ(IsInlined<T>::value, inlined);
- EXPECT_EQ(op(a), ref(a));
- return op(a);
+template <typename T> void test_op1(op1_t ref, double a, double expect) {
+ bool need_ref = std::is_same_v<T,CallOp1>;
+ T op = need_ref ? T(ref) : T(nullptr);
+ EXPECT_DOUBLE_EQ(ref(a), expect);
+ EXPECT_DOUBLE_EQ(op(a), expect);
};
-template <typename T> double test_op2(op2_t ref, double a, double b, bool inlined) {
- T op(ref);
- EXPECT_EQ(IsInlined<T>::value, inlined);
- EXPECT_EQ(op(a,b), ref(a,b));
- return op(a,b);
+template <typename T> void test_op2(op2_t ref, double a, double b, double expect) {
+ bool need_ref = std::is_same_v<T,CallOp2>;
+ T op = need_ref ? T(ref) : T(nullptr);
+ EXPECT_DOUBLE_EQ(ref(a, b), expect);
+ EXPECT_DOUBLE_EQ(op(a, b), expect);
};
op1_t as_op1(const vespalib::string &str) {
@@ -63,6 +65,8 @@ TEST(InlineOperationTest, op1_lambdas_are_recognized) {
EXPECT_EQ(as_op1("relu(a)"), &Relu::f);
EXPECT_EQ(as_op1("sigmoid(a)"), &Sigmoid::f);
EXPECT_EQ(as_op1("elu(a)"), &Elu::f);
+ //-------------------------------------------
+ EXPECT_EQ(as_op1("1/a"), &Inv::f);
}
TEST(InlineOperationTest, op1_lambdas_are_recognized_with_different_parameter_names) {
@@ -121,11 +125,36 @@ TEST(InlineOperationTest, generic_op2_wrapper_works) {
EXPECT_EQ(op(3,7), 10);
}
+TEST(InlineOperationTest, op1_typifier_forwards_return_value_correctly) {
+ auto a = typify_invoke<1,TypifyOp1,AsValue>(Neg::f);
+ auto b = typify_invoke<1,TypifyOp1,AsRef>(Neg::f);
+ EXPECT_EQ(a, my_value);
+ EXPECT_EQ(b, my_value);
+ bool same_memory = (&(typify_invoke<1,TypifyOp1,AsRef>(Neg::f)) == &my_value);
+ EXPECT_EQ(same_memory, true);
+}
+
+TEST(InlineOperationTest, op2_typifier_forwards_return_value_correctly) {
+ auto a = typify_invoke<1,TypifyOp2,AsValue>(Add::f);
+ auto b = typify_invoke<1,TypifyOp2,AsRef>(Add::f);
+ EXPECT_EQ(a, my_value);
+ EXPECT_EQ(b, my_value);
+ bool same_memory = (&(typify_invoke<1,TypifyOp2,AsRef>(Add::f)) == &my_value);
+ EXPECT_EQ(same_memory, true);
+}
+
+TEST(InlineOperationTest, inline_op1_example_works) {
+ op1_t ignored = nullptr;
+ InlineOp1<Inv> op(ignored);
+ EXPECT_EQ(op(1.0), 1.0);
+ EXPECT_EQ(op(8.0), 0.125);
+}
+
TEST(InlineOperationTest, inline_op2_example_works) {
op2_t ignored = nullptr;
InlineOp2<Add> op(ignored);
- EXPECT_EQ(op(2,3), 5);
- EXPECT_EQ(op(3,7), 10);
+ EXPECT_EQ(op(2.0, 3.0), 5.0);
+ EXPECT_EQ(op(3.0, 7.0), 10.0);
}
TEST(InlineOperationTest, parameter_swap_wrapper_works) {
@@ -137,20 +166,151 @@ TEST(InlineOperationTest, parameter_swap_wrapper_works) {
EXPECT_EQ(swap_op(3,7), 4);
}
-TEST(InlineOperationTest, resolved_op1_works) {
- auto a = TypifyOp1::resolve(Neg::f, [](auto t){ return test_op1<typename decltype(t)::type>(Neg::f, 2.0, false); });
- // putting the lambda inside the EXPECT does not work
- EXPECT_EQ(a, -2.0);
+//-----------------------------------------------------------------------------
+
+TEST(InlineOperationTest, op1_exp_is_inlined) {
+ TypifyOp1::resolve(Exp::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp1<Exp>>;
+ op1_t ref = Exp::f;
+ EXPECT_TRUE(type_ok);
+ test_op1<T>(ref, 2.0, std::exp(2.0));
+ test_op1<T>(ref, 3.0, std::exp(3.0));
+ test_op1<T>(ref, 7.0, std::exp(7.0));
+ });
+}
+
+TEST(InlineOperationTest, op1_inv_is_inlined) {
+ TypifyOp1::resolve(Inv::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp1<Inv>>;
+ op1_t ref = Inv::f;
+ EXPECT_TRUE(type_ok);
+ test_op1<T>(ref, 2.0, 1.0/2.0);
+ test_op1<T>(ref, 4.0, 1.0/4.0);
+ test_op1<T>(ref, 8.0, 1.0/8.0);
+ });
+}
+
+TEST(InlineOperationTest, op1_sqrt_is_inlined) {
+ TypifyOp1::resolve(Sqrt::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp1<Sqrt>>;
+ op1_t ref = Sqrt::f;
+ EXPECT_TRUE(type_ok);
+ test_op1<T>(ref, 2.0, sqrt(2.0));
+ test_op1<T>(ref, 4.0, sqrt(4.0));
+ test_op1<T>(ref, 64.0, sqrt(64.0));
+ });
+}
+
+TEST(InlineOperationTest, op1_tanh_is_inlined) {
+ TypifyOp1::resolve(Tanh::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp1<Tanh>>;
+ op1_t ref = Tanh::f;
+ EXPECT_TRUE(type_ok);
+ test_op1<T>(ref, 0.1, std::tanh(0.1));
+ test_op1<T>(ref, 0.3, std::tanh(0.3));
+ test_op1<T>(ref, 0.7, std::tanh(0.7));
+ });
+}
+
+TEST(InlineOperationTest, op1_neg_is_not_inlined) {
+ TypifyOp1::resolve(Neg::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,CallOp1>;
+ op1_t ref = Neg::f;
+ EXPECT_TRUE(type_ok);
+ test_op1<T>(ref, 3.0, -3.0);
+ test_op1<T>(ref, 5.0, -5.0);
+ test_op1<T>(ref, -2.0, 2.0);
+ });
+}
+
+//-----------------------------------------------------------------------------
+
+TEST(InlineOperationTest, op2_add_is_inlined) {
+ TypifyOp2::resolve(Add::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp2<Add>>;
+ op2_t ref = Add::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 2.0, 2.0, 4.0);
+ test_op2<T>(ref, 3.0, 8.0, 11.0);
+ test_op2<T>(ref, 7.0, 1.0, 8.0);
+ });
+}
+
+TEST(InlineOperationTest, op2_div_is_inlined) {
+ TypifyOp2::resolve(Div::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp2<Div>>;
+ op2_t ref = Div::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 2.0, 2.0, 1.0);
+ test_op2<T>(ref, 3.0, 8.0, 3.0 / 8.0);
+ test_op2<T>(ref, 7.0, 5.0, 7.0 / 5.0);
+ });
+}
+
+TEST(InlineOperationTest, op2_mul_is_inlined) {
+ TypifyOp2::resolve(Mul::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp2<Mul>>;
+ op2_t ref = Mul::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 2.0, 2.0, 4.0);
+ test_op2<T>(ref, 3.0, 8.0, 24.0);
+ test_op2<T>(ref, 7.0, 5.0, 35.0);
+ });
+}
+
+TEST(InlineOperationTest, op2_pow_is_inlined) {
+ TypifyOp2::resolve(Pow::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp2<Pow>>;
+ op2_t ref = Pow::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 2.0, 2.0, std::pow(2.0, 2.0));
+ test_op2<T>(ref, 3.0, 8.0, std::pow(3.0, 8.0));
+ test_op2<T>(ref, 7.0, 5.0, std::pow(7.0, 5.0));
+ });
+}
+
+TEST(InlineOperationTest, op2_sub_is_inlined) {
+ TypifyOp2::resolve(Sub::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,InlineOp2<Sub>>;
+ op2_t ref = Sub::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 3.0, 2.0, 1.0);
+ test_op2<T>(ref, 3.0, 8.0, -5.0);
+ test_op2<T>(ref, 7.0, 5.0, 2.0);
+ });
}
-TEST(InlineOperationTest, resolved_op2_works) {
- auto a = TypifyOp2::resolve(Add::f, [](auto t){ return test_op2<typename decltype(t)::type>(Add::f, 2.0, 5.0, true); });
- auto b = TypifyOp2::resolve(Mul::f, [](auto t){ return test_op2<typename decltype(t)::type>(Mul::f, 5.0, 3.0, true); });
- auto c = TypifyOp2::resolve(Sub::f, [](auto t){ return test_op2<typename decltype(t)::type>(Sub::f, 8.0, 5.0, false); });
- // putting the lambda inside the EXPECT does not work
- EXPECT_EQ(a, 7.0);
- EXPECT_EQ(b, 15.0);
- EXPECT_EQ(c, 3.0);
+TEST(InlineOperationTest, op2_mod_is_not_inlined) {
+ TypifyOp2::resolve(Mod::f, [](auto t)
+ {
+ using T = typename decltype(t)::type;
+ bool type_ok = std::is_same_v<T,CallOp2>;
+ op2_t ref = Mod::f;
+ EXPECT_TRUE(type_ok);
+ test_op2<T>(ref, 3.0, 2.0, std::fmod(3.0, 2.0));
+ test_op2<T>(ref, 3.0, 8.0, std::fmod(3.0, 8.0));
+ test_op2<T>(ref, 7.0, 5.0, std::fmod(7.0, 5.0));
+ });
}
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/eval/src/vespa/eval/eval/inline_operation.h b/eval/src/vespa/eval/eval/inline_operation.h
index 71e81b223e2..860f2330f57 100644
--- a/eval/src/vespa/eval/eval/inline_operation.h
+++ b/eval/src/vespa/eval/eval/inline_operation.h
@@ -4,6 +4,7 @@
#include "operation.h"
#include <vespa/vespalib/util/typify.h>
+#include <cmath>
namespace vespalib::eval::operation {
@@ -15,11 +16,38 @@ struct CallOp1 {
double operator()(double a) const { return my_op1(a); }
};
+template <typename T> struct InlineOp1;
+template <> struct InlineOp1<Exp> {
+ InlineOp1(op1_t) {}
+ template <typename A> constexpr auto operator()(A a) const { return exp(a); }
+};
+template <> struct InlineOp1<Inv> {
+ InlineOp1(op1_t) {}
+ template <typename A> constexpr auto operator()(A a) const { return (1/a); }
+};
+template <> struct InlineOp1<Sqrt> {
+ InlineOp1(op1_t) {}
+ template <typename A> constexpr auto operator()(A a) const { return std::sqrt(a); }
+};
+template <> struct InlineOp1<Tanh> {
+ InlineOp1(op1_t) {}
+ template <typename A> constexpr auto operator()(A a) const { return std::tanh(a); }
+};
+
struct TypifyOp1 {
template <typename T> using Result = TypifyResultType<T>;
template <typename F> static decltype(auto) resolve(op1_t value, F &&f) {
- (void) value;
- return f(Result<CallOp1>());
+ if (value == Exp::f) {
+ return f(Result<InlineOp1<Exp>>());
+ } else if (value == Inv::f) {
+ return f(Result<InlineOp1<Inv>>());
+ } else if (value == Sqrt::f) {
+ return f(Result<InlineOp1<Sqrt>>());
+ } else if (value == Tanh::f) {
+ return f(Result<InlineOp1<Tanh>>());
+ } else {
+ return f(Result<CallOp1>());
+ }
}
};
@@ -44,18 +72,36 @@ template <> struct InlineOp2<Add> {
InlineOp2(op2_t) {}
template <typename A, typename B> constexpr auto operator()(A a, B b) const { return (a+b); }
};
+template <> struct InlineOp2<Div> {
+ InlineOp2(op2_t) {}
+ template <typename A, typename B> constexpr auto operator()(A a, B b) const { return (a/b); }
+};
template <> struct InlineOp2<Mul> {
InlineOp2(op2_t) {}
template <typename A, typename B> constexpr auto operator()(A a, B b) const { return (a*b); }
};
+template <> struct InlineOp2<Pow> {
+ InlineOp2(op2_t) {}
+ template <typename A, typename B> constexpr auto operator()(A a, B b) const { return std::pow(a,b); }
+};
+template <> struct InlineOp2<Sub> {
+ InlineOp2(op2_t) {}
+ template <typename A, typename B> constexpr auto operator()(A a, B b) const { return (a-b); }
+};
struct TypifyOp2 {
template <typename T> using Result = TypifyResultType<T>;
template <typename F> static decltype(auto) resolve(op2_t value, F &&f) {
if (value == Add::f) {
return f(Result<InlineOp2<Add>>());
+ } else if (value == Div::f) {
+ return f(Result<InlineOp2<Div>>());
} else if (value == Mul::f) {
return f(Result<InlineOp2<Mul>>());
+ } else if (value == Pow::f) {
+ return f(Result<InlineOp2<Pow>>());
+ } else if (value == Sub::f) {
+ return f(Result<InlineOp2<Sub>>());
} else {
return f(Result<CallOp2>());
}
diff --git a/eval/src/vespa/eval/eval/operation.cpp b/eval/src/vespa/eval/eval/operation.cpp
index 581f65c0e31..bbd37ab68b2 100644
--- a/eval/src/vespa/eval/eval/operation.cpp
+++ b/eval/src/vespa/eval/eval/operation.cpp
@@ -49,6 +49,8 @@ double IsNan::f(double a) { return std::isnan(a) ? 1.0 : 0.0; }
double Relu::f(double a) { return std::max(a, 0.0); }
double Sigmoid::f(double a) { return 1.0 / (1.0 + std::exp(-1.0 * a)); }
double Elu::f(double a) { return (a < 0) ? std::exp(a) - 1 : a; }
+//-----------------------------------------------------------------------------
+double Inv::f(double a) { return (1 / a); }
namespace {
@@ -102,6 +104,8 @@ std::map<vespalib::string,op1_t> make_op1_map() {
add_op1(map, "relu(a)", Relu::f);
add_op1(map, "sigmoid(a)", Sigmoid::f);
add_op1(map, "elu(a)", Elu::f);
+ //-------------------------------------
+ add_op1(map, "1/a", Inv::f);
return map;
}
diff --git a/eval/src/vespa/eval/eval/operation.h b/eval/src/vespa/eval/eval/operation.h
index a80193e704d..b00bb5e26fc 100644
--- a/eval/src/vespa/eval/eval/operation.h
+++ b/eval/src/vespa/eval/eval/operation.h
@@ -48,6 +48,8 @@ struct IsNan { static double f(double a); };
struct Relu { static double f(double a); };
struct Sigmoid { static double f(double a); };
struct Elu { static double f(double a); };
+//-----------------------------------------------------------------------------
+struct Inv { static double f(double a); };
using op1_t = double (*)(double);
using op2_t = double (*)(double, double);