Add naive implementation and test/benchmark

This commit is contained in:
Artem Zyktin 2026-05-31 02:25:12 +08:00
parent 7960ad6cf6
commit 28e60f22e6
4 changed files with 488 additions and 45 deletions

View File

@ -44,50 +44,6 @@ project "gtest"
filter "system:macosx"
pic "on"
project "benchmark"
location "build"
kind "StaticLib"
language "C++"
cppdialect "C++20"
staticruntime "on"
targetdir ("bin/" .. outputdir .. "/output")
objdir ("bin/" .. outputdir .. "/intermediate")
files {
"thirdparty/benchmark/src/**.cc",
}
removefiles {
"thirdparty/benchmark/src/benchmark_main.cc",
}
includedirs {
"thirdparty/benchmark/include",
"thirdparty/benchmark/src",
}
defines { "BENCHMARK_STATIC_DEFINE", "HAVE_STD_REGEX" }
filter "configurations:debug"
runtime "Debug"
symbols "on"
optimize "off"
filter "configurations:release"
runtime "Release"
symbols "on"
optimize "Speed"
filter "system:windows"
systemversion "latest"
filter "system:linux"
pic "on"
filter "system:macosx"
pic "on"
project "gtest_main"
location "build"
kind "StaticLib"
@ -120,6 +76,51 @@ project "gtest_main"
filter "system:windows"
systemversion "latest"
project "benchmark"
location "build"
kind "StaticLib"
language "C++"
cppdialect "C++20"
staticruntime "on"
targetdir ("bin/" .. outputdir .. "/output")
objdir ("bin/" .. outputdir .. "/intermediate")
files {
"thirdparty/benchmark/src/**.cc",
}
removefiles {
"thirdparty/benchmark/src/benchmark_main.cc",
}
includedirs {
"thirdparty/benchmark/include",
"thirdparty/benchmark/src",
}
defines { "BENCHMARK_STATIC_DEFINE", "HAVE_STD_REGEX" }
filter "configurations:debug"
runtime "Debug"
symbols "on"
optimize "off"
filter "configurations:release"
defines { "NDEBUG" }
runtime "Release"
symbols "on"
optimize "Speed"
filter "system:windows"
systemversion "latest"
filter "system:linux"
pic "on"
filter "system:macosx"
pic "on"
project "tenvex"
location "build"
kind "None"
@ -176,6 +177,7 @@ project "tenvex_tests"
includedirs {
"src/tenvex",
"src/naive",
"thirdparty/gtest/googletest/include",
}
@ -222,6 +224,7 @@ project "tenvex_bench"
}
includedirs {
"src/naive",
"src/tenvex",
"thirdparty/benchmark/include",
}
@ -240,7 +243,7 @@ project "tenvex_bench"
runtime "Release"
symbols "on"
optimize "Speed"
defines { "TENVEX_RELEASE" }
defines { "TENVEX_RELEASE", "NDEBUG" }
filter "system:windows"
systemversion "latest"

View File

@ -0,0 +1,92 @@
#include "naive_vec4.h"
#include <benchmark/benchmark.h>
#include <vector>
#include <random>
using namespace naive;
// File-local; mirrors make_vecs from bench_vec4.cpp with the same seed,
// so the naive and tenvex benchmarks run on identical input data.
static std::vector<vec4> make_vecs(int n, unsigned seed)
{
std::mt19937 rng(seed);
std::uniform_real_distribution<float> d(-1.0f, 1.0f);
std::vector<vec4> v;
v.reserve(n);
for (int i = 0; i < n; ++i)
v.push_back(vec4 { d(rng), d(rng), d(rng), 0.0f });
return v;
}
static void BM_Naive_Dot3_Latency(benchmark::State& state)
{
vec4 a { 1.0f, 2.0f, 3.0f, 0.0f };
vec4 b { 0.5f, 0.5f, 0.5f, 0.0f };
float acc = 0.0f;
for (auto _ : state)
{
vec4 bb = b + vec4 { acc, 0.0f, 0.0f, 0.0f };
float d = dot3(a, bb);
acc = d * 1e-7f;
benchmark::DoNotOptimize(acc);
}
}
BENCHMARK(BM_Naive_Dot3_Latency);
static void BM_Naive_Norm3_Latency(benchmark::State& state)
{
vec4 v { 1.0f, 2.0f, 3.0f, 1.0f };
for (auto _ : state)
{
v = norm3(v) + vec4 { 1e-3f, 2e-3f, 3e-3f, 0.0f };
benchmark::DoNotOptimize(v);
}
}
BENCHMARK(BM_Naive_Norm3_Latency);
static void BM_Naive_Dot3_Throughput(benchmark::State& state)
{
const auto a = make_vecs(1024, 1);
const auto b = make_vecs(1024, 2);
std::size_t i = 0;
for (auto _ : state)
{
float d = dot3(a[i], b[i]);
benchmark::DoNotOptimize(d);
i = (i + 1) & 1023;
}
}
BENCHMARK(BM_Naive_Dot3_Throughput);
static void BM_Naive_Compound(benchmark::State& state)
{
const auto va = make_vecs(1024, 1);
const auto vb = make_vecs(1024, 2);
const auto vc = make_vecs(1024, 3);
std::size_t i = 0;
for (auto _ : state)
{
vec4 r = norm3(va[i] + vb[i] * 2.0f) * dot3(vb[i], vc[i]) + vc[i] * 3.0f;
benchmark::DoNotOptimize(r);
i = (i + 1) & 1023;
}
}
BENCHMARK(BM_Naive_Compound);
static void BM_Naive_FacingSameWay(benchmark::State& state)
{
vec4 fa = norm3(vec4 { 1.0f, 0.0f, 0.2f, 0.0f });
vec4 fb = vec4 { 0.9f, 0.1f, 0.1f, 0.0f };
float jitter = 0.0f;
for (auto _ : state)
{
vec4 f = norm3(fb + vec4 { jitter, 0.0f, 0.0f, 0.0f });
float c = dot3(fa, f);
bool same = c > 0.95f;
jitter = c * 1e-6f;
benchmark::DoNotOptimize(same);
}
}
BENCHMARK(BM_Naive_FacingSameWay);

92
src/naive/naive_vec4.h Normal file
View File

@ -0,0 +1,92 @@
#pragma once
#include <cmath>
namespace naive
{
struct vec4
{
vec4() noexcept = default;
vec4(float x, float y, float z, float w = 0.0f) noexcept
: _x(x), _y(y), _z(z), _w(w)
{
}
[[nodiscard]] float x() const noexcept { return _x; }
[[nodiscard]] float y() const noexcept { return _y; }
[[nodiscard]] float z() const noexcept { return _z; }
[[nodiscard]] float w() const noexcept { return _w; }
private:
float _x, _y, _z, _w;
};
[[nodiscard]] inline vec4 operator+(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 operator-(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 operator*(vec4 v, float s) noexcept
{
return { v.x() * s, v.y() * s, v.z() * s, v.w() * s };
}
[[nodiscard]] inline vec4 operator*(float s, vec4 v) noexcept
{
return v * s;
}
[[nodiscard]] inline vec4 operator/(vec4 v, float s) noexcept
{
const float inv = 1.0f / s; // one division, then multiplies
return { v.x() * inv, v.y() * inv, v.z() * inv, v.w() * inv };
}
[[nodiscard]] inline float dot3(vec4 a, vec4 b) noexcept
{
return a.x() * b.x() + a.y() * b.y() + a.z() * b.z();
}
[[nodiscard]] inline vec4 cross3(vec4 a, vec4 b) noexcept
{
return {
a.y() * b.z() - a.z() * b.y(),
a.z() * b.x() - a.x() * b.z(),
a.x() * b.y() - a.y() * b.x(),
0.0f
};
}
[[nodiscard]] inline float magnitude3(vec4 v) noexcept
{
return std::sqrt(dot3(v, v));
}
[[nodiscard]] inline vec4 norm3(vec4 v) noexcept
{
const float inv = 1.0f / magnitude3(v); // normalize xyz, keep w
return { v.x() * inv, v.y() * inv, v.z() * inv, v.w() };
}
inline bool operator==(vec4 a, vec4 b) noexcept
{
return a.x() == b.x() && a.y() == b.y()
&& a.z() == b.z() && a.w() == b.w();
}
inline bool approx_eq(vec4 a, vec4 b, float eps = 1e-6f) noexcept
{
return std::fabs(a.x() - b.x()) <= eps
&& std::fabs(a.y() - b.y()) <= eps
&& std::fabs(a.z() - b.z()) <= eps
&& std::fabs(a.w() - b.w()) <= eps;
}
}

256
src/tests/naive_tests.cpp Normal file
View File

@ -0,0 +1,256 @@
#include "naive_vec4.h"
#include "gtest/gtest.h"
#include <cmath>
namespace naive
{
TEST(naive_vec4, add)
{
vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f };
vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f };
vec4 check = { 5.0f, 5.0f, 5.0f, 5.0f };
vec4 result = a + b;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, mixed_type_add)
{
vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f };
vec4 b = { 1.0f, 1.0f, 1.0f, 0.0f };
vec4 c = { 2.0f, 0.0f, 0.0f, 0.0f };
vec4 check = { 4.0f, 3.0f, 4.0f, 0.0f };
vec4 result = a + b + c;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, sub)
{
vec4 a = { 5.0f, 5.0f, 5.0f, 5.0f };
vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f };
vec4 check = { 1.0f, 2.0f, 3.0f, 4.0f };
vec4 result = a - b;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, mult_a)
{
vec4 a = { 1.0f, 2.0f, 0.0f, 0.0f };
float b = 3.0f;
vec4 check = { 3.0f, 6.0f, 0.0f, 0.0f };
vec4 result = a * b;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, mult_b)
{
float a = 2.0f;
vec4 b = { 1.0f, 0.0f, 2.0f, 0.0f };
vec4 check = { 2.0f, 0.0f, 4.0f, 0.0f };
vec4 result = a * b;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, div)
{
vec4 a = { 3.0f, 6.0f, 0.0f, 0.0f };
float b = 3.0f;
vec4 check = { 1.0f, 2.0f, 0.0f, 0.0f };
vec4 result = a / b;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, dot3)
{
vec4 a = { 1.0f, 0.0f, 3.0f, 1.0f };
vec4 b = { 1.0f, 2.0f, 3.0f, 4.0f };
float check = 10.0f;
float result = dot3(a, b);
EXPECT_FLOAT_EQ(check, result);
}
TEST(naive_vec4, cross)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f };
vec4 check = { 0.0f, 0.0f, 1.0f, 0.0f };
vec4 result = cross3(a, b);
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, norm_a)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 1.0f };
vec4 check = { 1.0f, 0.0f, 0.0f, 1.0f };
vec4 result = norm3(a);
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, norm_b)
{
vec4 a = { 1.0f, 0.0f, 1.0f, 1.0f };
vec4 check = { 0.707106f, 0.0f, 0.707106f, 1.0f };
vec4 result = norm3(a);
EXPECT_TRUE(approx_eq(check, result, 1e-3f));
}
TEST(naive_vec4, magnitude_a)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
float check = 1.0f;
float result = magnitude3(a);
EXPECT_FLOAT_EQ(check, result);
}
TEST(naive_vec4, magnitude_b)
{
vec4 a = { 1.0f, 0.0f, 1.0f, 0.0f };
float check = 1.4142135f;
float result = magnitude3(a);
EXPECT_FLOAT_EQ(check, result);
}
TEST(naive_vec4, expression_a)
{
vec4 a = { 1.0f, 1.0f, 1.0f, 1.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 3.0f };
vec4 c = { 1.15f, 0.0f, 11.0f, 0.0f };
vec4 check = { 3.45f, 0.0f, 33.0f, 0.0f };
vec4 result = dot3(a, b) * 3.0f * c;
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, expression_b)
{
vec4 a = { 1.0f, 1.0f, 1.0f, 0.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f };
vec4 c = { 1.15f, 0.0f, 11.0f, 0.0f };
vec4 check = { 33.0f, 0.0f, -3.45f, 0.0f };
vec4 result = dot3(a, b) * 3.0f * cross3(b, c);
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, expression_c)
{
vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f };
vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f };
vec4 c = { 1.0f, 0.0f, 1.0f, 0.0f };
vec4 check = { 7.24264050f, 5.65685463f, 10.0710678f, 0.0f };
vec4 tmp1 = norm3(a + b * 2.0f) * dot3(b, c);
vec4 tmp2 = c * 3.0f;
vec4 result = tmp1 + tmp2;
EXPECT_TRUE(approx_eq(check, result, 1e-3f));
}
TEST(naive_vec4, expression_d)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
vec4 b = { 0.0f, 2.0f, 0.0f, 0.0f };
vec4 c = { 0.0f, 0.0f, 3.0f, 0.0f };
vec4 result = (a + b) * 2.0f + c - a;
vec4 check = { 1.0f, 4.0f, 3.0f, 0.0f };
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, expression_from_temporaries)
{
vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f };
vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f };
vec4 check = { 2.0f, 4.0f, 6.0f, 0.0f };
vec4 result = (a + b) + (a - b);
EXPECT_TRUE(approx_eq(check, result));
}
TEST(naive_vec4, cross_anticommutative)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f };
EXPECT_TRUE(approx_eq(cross3(a, b), cross3(b, a) * -1.0f));
}
TEST(naive_vec4, w_is_zero_with_nonzero_input_w)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 5.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 7.0f };
vec4 r = cross3(a, b);
EXPECT_FLOAT_EQ(r.w(), 0.0f);
EXPECT_FLOAT_EQ(r.z(), 1.0f);
}
TEST(naive_vec4, preserves_nontrivial_w)
{
vec4 a = { 0.0f, 3.0f, 0.0f, 9.0f };
vec4 r = norm3(a);
EXPECT_FLOAT_EQ(r.y(), 1.0f);
EXPECT_FLOAT_EQ(r.w(), 9.0f);
}
TEST(naive_vec4, dot_commutative)
{
vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f };
vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f };
EXPECT_FLOAT_EQ(dot3(a, b), dot3(b, a));
}
TEST(naive_vec4, norm_magnitude)
{
vec4 a = { 3.0f, 1.0f, 4.0f, 0.0f };
EXPECT_NEAR(magnitude3(norm3(a)), 1.0f, 1e-6f);
}
TEST(naive_vec4, zero_vector_is_nan)
{
vec4 z = { 0.0f, 0.0f, 0.0f, 0.0f };
vec4 r = norm3(z);
EXPECT_TRUE(std::isnan(r.x()));
}
TEST(naive_vec4, dot_perpendicular)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f };
EXPECT_FLOAT_EQ(dot3(a, b), 0.0f);
}
TEST(naive_vec4, cross_parallel)
{
vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f };
vec4 b = { 2.0f, 0.0f, 0.0f, 0.0f };
vec4 check = { 0.0f, 0.0f, 0.0f, 0.0f };
EXPECT_TRUE(approx_eq(cross3(a, b), check));
}
}