From e4b5e656e74fda1c9f7d7752ef40a87946b2f1f1 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Wed, 3 Jun 2026 17:20:14 +0800 Subject: [PATCH 1/4] Add dot4 expression --- src/tenvex/expressions/core.h | 8 ++++++ src/tenvex/expressions/dot4.h | 43 ++++++++++++++++++++++++++++ src/tenvex/expressions/dot4_impl.hpp | 30 +++++++++++++++++++ src/tenvex/tenvex.h | 1 + 4 files changed, 82 insertions(+) create mode 100644 src/tenvex/expressions/dot4.h create mode 100644 src/tenvex/expressions/dot4_impl.hpp diff --git a/src/tenvex/expressions/core.h b/src/tenvex/expressions/core.h index dee9a82..69c44f2 100644 --- a/src/tenvex/expressions/core.h +++ b/src/tenvex/expressions/core.h @@ -87,6 +87,14 @@ vf4 dot3(vf4 l, vf4 r) noexcept return _mm_shuffle_ps(xyz, xyz, _MM_SHUFFLE(0, 0, 0, 0)); } +[[nodiscard]] TNVX_INLINE +vf4 dot4(vf4 l, vf4 r) noexcept +{ + vf4 t = _mm_mul_ps(l, r); + vf4 h = _mm_hadd_ps(t, t); + return _mm_hadd_ps(h, h); +} + [[nodiscard]] TNVX_INLINE vf4 magnitude3(vf4 v) noexcept { diff --git a/src/tenvex/expressions/dot4.h b/src/tenvex/expressions/dot4.h new file mode 100644 index 0000000..59a9f9e --- /dev/null +++ b/src/tenvex/expressions/dot4.h @@ -0,0 +1,43 @@ +#pragma once + +#include "core.h" +#include "expression.h" +#include "traits.h" +#include "concepts.h" + +namespace tnvx +{ + +template +struct Dot4 : Expr> +{ + TNVX_INLINE + Dot4(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] TNVX_INLINE + vf4 eval() const noexcept; + + [[nodiscard]] TNVX_INLINE + operator float() const noexcept; + +private: + tnvx_ref_or_value_t _l; + tnvx_ref_or_value_t _r; +}; + +template inline constexpr bool is_scalar_expr> = true; + +template +[[nodiscard]] TNVX_INLINE +Dot3 dot4(const L& l, const R& r) noexcept; + +template +TNVX_INLINE +Dot3 dot4(const L& l, const R& r) noexcept +{ + return { l, r }; +} + +} + +#include "dot4_impl.hpp" diff --git a/src/tenvex/expressions/dot4_impl.hpp b/src/tenvex/expressions/dot4_impl.hpp new file mode 100644 index 0000000..5161d37 --- /dev/null +++ b/src/tenvex/expressions/dot4_impl.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "dot4.h" + +namespace tnvx +{ + +template +TNVX_INLINE +Dot4::Dot4(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l) + , _r(r) +{ +} + +template +TNVX_INLINE +vf4 Dot4::eval() const noexcept +{ + return detail::dot4(_l.eval(), _r.eval()); +} + +template +TNVX_INLINE +Dot4::operator float() const noexcept +{ + return detail::to_float(eval()); +} + +} diff --git a/src/tenvex/tenvex.h b/src/tenvex/tenvex.h index ad2e4ba..c9e9162 100644 --- a/src/tenvex/tenvex.h +++ b/src/tenvex/tenvex.h @@ -7,6 +7,7 @@ #include "expressions/mul.h" #include "expressions/div.h" #include "expressions/dot3.h" +#include "expressions/dot4.h" #include "expressions/magnitude3.h" #include "expressions/norm3.h" #include "expressions/cross3.h" From 260c166e685c95dad094ace771f54cebc4502f63 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Wed, 3 Jun 2026 17:47:15 +0800 Subject: [PATCH 2/4] Add naive implementation --- src/naive/naive_vec4.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/naive/naive_vec4.h b/src/naive/naive_vec4.h index ae3ff46..1e37c52 100644 --- a/src/naive/naive_vec4.h +++ b/src/naive/naive_vec4.h @@ -54,6 +54,11 @@ private: return a.x() * b.x() + a.y() * b.y() + a.z() * b.z(); } +[[nodiscard]] inline float dot4(vec4 a, vec4 b) noexcept +{ + return a.x() * b.x() + a.y() * b.y() + a.z() * b.z() + a.w() * b.w(); +} + [[nodiscard]] inline vec4 cross3(vec4 a, vec4 b) noexcept { return { From d8c6064695526b2cb7b1e789f554fa6a44e19670 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Wed, 3 Jun 2026 17:47:22 +0800 Subject: [PATCH 3/4] Add tests --- src/tests/naive_tests.cpp | 36 +++++++++++++++++++++ src/tests/vec4_tests.cpp | 67 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/tests/naive_tests.cpp b/src/tests/naive_tests.cpp index e78260e..8378006 100644 --- a/src/tests/naive_tests.cpp +++ b/src/tests/naive_tests.cpp @@ -283,4 +283,40 @@ TEST(naive_vec4, neg_compound) EXPECT_TRUE(approx_eq(a + (-b), a - b)); } +TEST(naive_vec4, dot4_basic) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f }; + EXPECT_FLOAT_EQ(dot4(a, b), 30.0f); +} + +TEST(naive_vec4, dot4_w_matters) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f }; + EXPECT_FLOAT_EQ(dot3(a, b), 14.0f); + EXPECT_FLOAT_EQ(dot4(a, b), 30.0f); +} + +TEST(naive_vec4, dot4_w_only) +{ + vec4 a = { 0.0f, 0.0f, 0.0f, 5.0f }; + vec4 b = { 0.0f, 0.0f, 0.0f, 3.0f }; + EXPECT_FLOAT_EQ(dot4(a, b), 15.0f); +} + +TEST(naive_vec4, dot4_commutative) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f }; + EXPECT_FLOAT_EQ(dot4(a, b), dot4(b, a)); +} + +TEST(naive_vec4, dot4_w_zero_matches_dot3) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f }; + vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f }; + EXPECT_FLOAT_EQ(dot4(a, b), dot3(a, b)); +} + } \ No newline at end of file diff --git a/src/tests/vec4_tests.cpp b/src/tests/vec4_tests.cpp index 10c1a13..98f83a8 100644 --- a/src/tests/vec4_tests.cpp +++ b/src/tests/vec4_tests.cpp @@ -337,4 +337,71 @@ TEST(vec4, neg_scalar_in_expression) EXPECT_TRUE(approx_eq(check, result)); } +TEST(vec4, dot4_basic) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f }; + float check = 30.0f; + float result = dot4(a, b); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, dot4_w_zero_matches_dot3) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f }; + vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f }; + + EXPECT_FLOAT_EQ(float(dot4(a, b)), float(dot3(a, b))); +} + +TEST(vec4, dot4_w_matters) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f }; + float check1 = 14.0f; + float result1 = dot3(a, b); + float check2 = 30.0f; + float result2 = dot4(a, b); + + EXPECT_FLOAT_EQ(check1, result1); + EXPECT_FLOAT_EQ(check2, result2); +} + +TEST(vec4, dot4_w_only) +{ + vec4 a = { 0.0f, 0.0f, 0.0f, 5.0f }; + vec4 b = { 0.0f, 0.0f, 0.0f, 3.0f }; + float check = 15.0f; + float result = dot4(a, b); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, dot4_perpendicular) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + float check = 0.0f; + float result = dot4(a, b); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, dot4_commutative) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f }; + + EXPECT_FLOAT_EQ(float(dot4(a, b)), float(dot4(b, a))); +} + +TEST(vec4, dot4_collapses) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + + EXPECT_TRUE((scalar_expr)); +} + } From 976eb84340c8addd0b2eba88c1a77f29a70aaa53 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Wed, 3 Jun 2026 17:47:32 +0800 Subject: [PATCH 4/4] Add benchmarks --- src/benchmarks/bench_naive.cpp | 40 ++++++++++++++++++++++++++++++++++ src/benchmarks/bench_vec4.cpp | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/benchmarks/bench_naive.cpp b/src/benchmarks/bench_naive.cpp index d1adbac..add5f67 100644 --- a/src/benchmarks/bench_naive.cpp +++ b/src/benchmarks/bench_naive.cpp @@ -20,6 +20,17 @@ static std::vector make_vecs(int n, unsigned seed) return v; } +static std::vector make_points(int n, unsigned seed) +{ + std::mt19937 rng(seed); + std::uniform_real_distribution d(-1.0f, 1.0f); + std::vector v; + v.reserve(n); + for (int i = 0; i < n; ++i) + v.push_back(vec4{ d(rng), d(rng), d(rng), 1.0f }); + return v; +} + static void BM_Naive_Dot3_Latency(benchmark::State& state) { vec4 a { 1.0f, 2.0f, 3.0f, 0.0f }; @@ -90,3 +101,32 @@ static void BM_Naive_FacingSameWay(benchmark::State& state) } } BENCHMARK(BM_Naive_FacingSameWay); + +static void BM_Naive_Dot4_Latency(benchmark::State& state) +{ + vec4 a{ 1.0f, 2.0f, 3.0f, 1.0f }; + vec4 b{ 0.5f, 0.5f, 0.5f, 0.5f }; + float acc = 0.0f; + for (auto _ : state) + { + vec4 bb = b + vec4{ acc, 0.0f, 0.0f, 0.0f }; + float d = dot4(a, bb); + acc = d * 1e-7f; + benchmark::DoNotOptimize(acc); + } +} +BENCHMARK(BM_Naive_Dot4_Latency); + +static void BM_Naive_Dot4_Throughput(benchmark::State& state) +{ + const auto a = make_points(1024, 1); + const auto b = make_points(1024, 2); + std::size_t i = 0; + for (auto _ : state) + { + float d = dot4(a[i], b[i]); + benchmark::DoNotOptimize(d); + i = (i + 1) & 1023; + } +} +BENCHMARK(BM_Naive_Dot4_Throughput); diff --git a/src/benchmarks/bench_vec4.cpp b/src/benchmarks/bench_vec4.cpp index 365c091..c490aa5 100644 --- a/src/benchmarks/bench_vec4.cpp +++ b/src/benchmarks/bench_vec4.cpp @@ -20,6 +20,17 @@ static std::vector make_vecs(int n, unsigned seed) return v; } +inline std::vector make_points(int n, unsigned seed) +{ + std::mt19937 rng(seed); + std::uniform_real_distribution d(-1.0f, 1.0f); + std::vector v; + v.reserve(n); + for (int i = 0; i < n; ++i) + v.push_back(tnvx::vec4{ d(rng), d(rng), d(rng), 1.0f }); // w=1: point + return v; +} + // ===================================================================== // LATENCY: each iteration depends on the previous result (serial chain). // This is the metric for scattered everyday math: the result is needed @@ -133,3 +144,32 @@ static void BM_FacingSameWay(benchmark::State& state) } } BENCHMARK(BM_FacingSameWay); + +static void BM_Dot4_Latency(benchmark::State& state) +{ + vec4 a{ 1.0f, 2.0f, 3.0f, 1.0f }; + vec4 b{ 0.5f, 0.5f, 0.5f, 0.5f }; + float acc = 0.0f; + for (auto _ : state) + { + vec4 bb = b + vec4{ acc, 0.0f, 0.0f, 0.0f }; + float d = dot4(a, bb); + acc = d * 1e-7f; + benchmark::DoNotOptimize(acc); + } +} +BENCHMARK(BM_Dot4_Latency); + +static void BM_Dot4_Throughput(benchmark::State& state) +{ + const auto a = make_points(1024, 1); + const auto b = make_points(1024, 2); + std::size_t i = 0; + for (auto _ : state) + { + float d = dot4(a[i], b[i]); + benchmark::DoNotOptimize(d); + i = (i + 1) & 1023; + } +} +BENCHMARK(BM_Dot4_Throughput);