From a189a80cc25551af14ddce60ea98d03eebf6a697 Mon Sep 17 00:00:00 2001 From: Bram Veenboer Date: Fri, 1 Aug 2025 13:56:02 +0200 Subject: [PATCH] Initial commit --- .pre-commit-config.yaml | 10 ++++ .vscode/settings.json | 77 ++++++++++++++++++++++++++++++ CMakeLists.txt | 13 +++++ benchmarks/CMakeLists.txt | 10 ++++ benchmarks/benchmark_lookup.cpp | 13 +++++ benchmarks/benchmark_mkl.cpp | 9 ++++ benchmarks/benchmark_reference.cpp | 9 ++++ benchmarks/benchmark_utils.hpp | 76 +++++++++++++++++++++++++++++ include/trigdx/interface.hpp | 22 +++++++++ include/trigdx/lookup.hpp | 22 +++++++++ include/trigdx/lookup.tpp | 44 +++++++++++++++++ include/trigdx/mkl.hpp | 13 +++++ include/trigdx/reference.hpp | 13 +++++ src/CMakeLists.txt | 9 ++++ src/mkl.cpp | 16 +++++++ src/reference.cpp | 23 +++++++++ tests/CMakeLists.txt | 24 ++++++++++ tests/test_lookup.cpp | 19 ++++++++ tests/test_mkl.cpp | 10 ++++ tests/test_utils.hpp | 68 ++++++++++++++++++++++++++ 20 files changed, 500 insertions(+) create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 benchmarks/CMakeLists.txt create mode 100644 benchmarks/benchmark_lookup.cpp create mode 100644 benchmarks/benchmark_mkl.cpp create mode 100644 benchmarks/benchmark_reference.cpp create mode 100644 benchmarks/benchmark_utils.hpp create mode 100644 include/trigdx/interface.hpp create mode 100644 include/trigdx/lookup.hpp create mode 100644 include/trigdx/lookup.tpp create mode 100644 include/trigdx/mkl.hpp create mode 100644 include/trigdx/reference.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/mkl.cpp create mode 100644 src/reference.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/test_lookup.cpp create mode 100644 tests/test_mkl.cpp create mode 100644 tests/test_utils.hpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7eceb16 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v17.0.6 + hooks: + - id: clang-format + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + - id: cmake-lint \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a53fa15 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,77 @@ +{ + "files.associations": { + "*.cc": "cpp", + "*.tpp": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "queue": "cpp", + "stack": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c4a6ebc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) +project(trigdx LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +option(USE_MKL "Enable Intel MKL backend" OFF) + +include_directories(${PROJECT_SOURCE_DIR}/include) + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(benchmarks) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..228c610 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,10 @@ +add_executable(benchmark_reference benchmark_reference.cpp) +target_link_libraries(benchmark_reference PRIVATE trigdx) + +add_executable(benchmark_lookup benchmark_lookup.cpp) +target_link_libraries(benchmark_lookup PRIVATE trigdx) + +if(USE_MKL) + add_executable(benchmark_mkl benchmark_mkl.cpp) + target_link_libraries(benchmark_mkl PRIVATE trigdx) +endif() diff --git a/benchmarks/benchmark_lookup.cpp b/benchmarks/benchmark_lookup.cpp new file mode 100644 index 0000000..9961655 --- /dev/null +++ b/benchmarks/benchmark_lookup.cpp @@ -0,0 +1,13 @@ +#include + +#include "benchmark_utils.hpp" + +int main() { + benchmark_sinf>(); + benchmark_cosf>(); + benchmark_sincosf>(); + + benchmark_sinf>(); + benchmark_cosf>(); + benchmark_sincosf>(); +} diff --git a/benchmarks/benchmark_mkl.cpp b/benchmarks/benchmark_mkl.cpp new file mode 100644 index 0000000..955cfac --- /dev/null +++ b/benchmarks/benchmark_mkl.cpp @@ -0,0 +1,9 @@ +#include + +#include "benchmark_utils.hpp" + +int main() { + benchmark_sinf(); + benchmark_cosf(); + benchmark_sincosf(); +} diff --git a/benchmarks/benchmark_reference.cpp b/benchmarks/benchmark_reference.cpp new file mode 100644 index 0000000..d303286 --- /dev/null +++ b/benchmarks/benchmark_reference.cpp @@ -0,0 +1,9 @@ +#include + +#include "benchmark_utils.hpp" + +int main() { + benchmark_sinf(); + benchmark_cosf(); + benchmark_sincosf(); +} diff --git a/benchmarks/benchmark_utils.hpp b/benchmarks/benchmark_utils.hpp new file mode 100644 index 0000000..eb09b92 --- /dev/null +++ b/benchmarks/benchmark_utils.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include + +const size_t N = 1e7; + +inline void report(const std::string &name, double sec, double throughput) { + std::ios state(nullptr); + state.copyfmt(std::cout); + std::cout << std::setw(7) << name << " -> "; + std::cout << "time: "; + std::cout << std::fixed << std::setprecision(3) << std::setfill('0'); + std::cout << sec << " s, "; + std::cout << "throughput: " << throughput << " M elems/sec\n"; + std::cout.copyfmt(state); +} + +template inline void benchmark_sinf() { + std::vector x(N), s(N); + + for (size_t i = 0; i < N; ++i) + x[i] = (i % 360) * 0.0174533f; // degrees to radians + + Backend backend; + backend.init(); + + auto start = std::chrono::high_resolution_clock::now(); + backend.compute_sinf(N, x.data(), s.data()); + auto end = std::chrono::high_resolution_clock::now(); + + double sec = std::chrono::duration(end - start).count(); + double throughput = N / sec / 1e6; + + report("sinf", sec, throughput); +} + +template inline void benchmark_cosf() { + std::vector x(N), c(N); + + for (size_t i = 0; i < N; ++i) + x[i] = (i % 360) * 0.0174533f; // degrees to radians + + Backend backend; + backend.init(); + + auto start = std::chrono::high_resolution_clock::now(); + backend.compute_cosf(N, x.data(), c.data()); + auto end = std::chrono::high_resolution_clock::now(); + + double sec = std::chrono::duration(end - start).count(); + double throughput = N / sec / 1e6; + + report("cosf", sec, throughput); +} + +template inline void benchmark_sincosf() { + std::vector x(N), s(N), c(N); + + for (size_t i = 0; i < N; ++i) + x[i] = (i % 360) * 0.0174533f; // degrees to radians + + Backend backend; + backend.init(); + + auto start = std::chrono::high_resolution_clock::now(); + backend.compute_sincosf(N, x.data(), s.data(), c.data()); + auto end = std::chrono::high_resolution_clock::now(); + + double sec = std::chrono::duration(end - start).count(); + double throughput = N / sec / 1e6; + + report("sincosf", sec, throughput); +} diff --git a/include/trigdx/interface.hpp b/include/trigdx/interface.hpp new file mode 100644 index 0000000..8b57348 --- /dev/null +++ b/include/trigdx/interface.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +// Base interface for all math backends +class Backend { +public: + virtual ~Backend() = default; + + // Optional initialization + virtual void init() {} + + // Compute sine for n elements + virtual void compute_sinf(size_t n, const float *x, float *s) const = 0; + + // Compute cosine for n elements + virtual void compute_cosf(size_t n, const float *x, float *c) const = 0; + + // Compute sine and cosine for n elements + virtual void compute_sincosf(size_t n, const float *x, float *s, + float *c) const = 0; +}; diff --git a/include/trigdx/lookup.hpp b/include/trigdx/lookup.hpp new file mode 100644 index 0000000..8e4e232 --- /dev/null +++ b/include/trigdx/lookup.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "interface.hpp" + +template class LookupBackend : public Backend { +public: + void init() override; + void compute_sinf(size_t n, const float *x, float *s) const override; + void compute_cosf(size_t n, const float *x, float *c) const override; + void compute_sincosf(size_t n, const float *x, float *s, + float *c) const override; + +private: + std::vector lookup; + static constexpr size_t MASK = NR_SAMPLES - 1; + static constexpr float SCALE = NR_SAMPLES / (2.0f * float(M_PI)); +}; + +#include "lookup.tpp" // include implementation diff --git a/include/trigdx/lookup.tpp b/include/trigdx/lookup.tpp new file mode 100644 index 0000000..adbfe26 --- /dev/null +++ b/include/trigdx/lookup.tpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +template +void LookupBackend::init() { + lookup.resize(NR_SAMPLES); + for (size_t i = 0; i < NR_SAMPLES; ++i) + lookup[i] = std::sinf(i * (2.0f * float(M_PI) / NR_SAMPLES)); +} + +template +void LookupBackend::compute_sinf(size_t n, + const float* x, + float* s) const { + for (size_t i = 0; i < n; ++i) { + size_t idx = static_cast(x[i] * SCALE) & MASK; + s[i] = lookup[idx]; + } +} + +template +void LookupBackend::compute_cosf(size_t n, + const float* x, + float* c) const { + for (size_t i = 0; i < n; ++i) { + size_t idx = static_cast(x[i] * SCALE) & MASK; + size_t idx_cos = (idx + NR_SAMPLES / 4) & MASK; + c[i] = lookup[idx_cos]; + } +} + +template +void LookupBackend::compute_sincosf(size_t n, + const float* x, + float* s, + float* c) const { + for (size_t i = 0; i < n; ++i) { + size_t idx = static_cast(x[i] * SCALE) & MASK; + size_t idx_cos = (idx + NR_SAMPLES / 4) & MASK; + s[i] = lookup[idx]; + c[i] = lookup[idx_cos]; + } +} diff --git a/include/trigdx/mkl.hpp b/include/trigdx/mkl.hpp new file mode 100644 index 0000000..ee2709b --- /dev/null +++ b/include/trigdx/mkl.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "interface.hpp" + +class MKLBackend : public Backend { +public: + void compute_sinf(size_t n, const float *x, float *s) const override; + + void compute_cosf(size_t n, const float *x, float *c) const override; + + void compute_sincosf(size_t n, const float *x, float *s, + float *c) const override; +}; diff --git a/include/trigdx/reference.hpp b/include/trigdx/reference.hpp new file mode 100644 index 0000000..0259083 --- /dev/null +++ b/include/trigdx/reference.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "interface.hpp" + +class ReferenceBackend : public Backend { +public: + void compute_sinf(size_t n, const float *x, float *s) const override; + + void compute_cosf(size_t n, const float *x, float *c) const override; + + void compute_sincosf(size_t n, const float *x, float *s, + float *c) const override; +}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f30be2c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(trigdx reference.cpp) + +target_include_directories(trigdx PUBLIC ${PROJECT_SOURCE_DIR}/include) + +if(USE_MKL) + find_package(MKL REQUIRED) + target_sources(trigdx PRIVATE mkl.cpp) + target_link_libraries(trigdx PRIVATE MKL::MKL) +endif() diff --git a/src/mkl.cpp b/src/mkl.cpp new file mode 100644 index 0000000..6ea0a59 --- /dev/null +++ b/src/mkl.cpp @@ -0,0 +1,16 @@ +#include + +#include "trigdx/mkl.hpp" + +void MKLBackend::compute_sinf(size_t n, const float *x, float *s) const { + vmsSin(static_cast(n), x, s, VML_HA); +} + +void MKLBackend::compute_cosf(size_t n, const float *x, float *c) const { + vmsCos(static_cast(n), x, c, VML_HA); +} + +void MKLBackend::compute_sincosf(size_t n, const float *x, float *s, + float *c) const { + vmsSinCos(static_cast(n), x, s, c, VML_HA); +} diff --git a/src/reference.cpp b/src/reference.cpp new file mode 100644 index 0000000..e2ac788 --- /dev/null +++ b/src/reference.cpp @@ -0,0 +1,23 @@ +#include + +#include "trigdx/reference.hpp" + +void ReferenceBackend::compute_sinf(size_t n, const float *x, float *s) const { + for (size_t i = 0; i < n; ++i) { + s[i] = std::sinf(x[i]); + } +} + +void ReferenceBackend::compute_cosf(size_t n, const float *x, float *c) const { + for (size_t i = 0; i < n; ++i) { + c[i] = std::cosf(x[i]); + } +} + +void ReferenceBackend::compute_sincosf(size_t n, const float *x, float *s, + float *c) const { + for (size_t i = 0; i < n; ++i) { + s[i] = std::sinf(x[i]); + c[i] = std::cosf(x[i]); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..b6bf8b7 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +include(FetchContent) + +FetchContent_Declare( + catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.9.0) +FetchContent_MakeAvailable(catch2) + +# Lookup backend test +add_executable(test_lookup test_lookup.cpp) +target_link_libraries(test_lookup PRIVATE trigdx Catch2::Catch2WithMain) + +# MKL backend test +if(USE_MKL) + add_executable(test_mkl test_mkl.cpp) + target_link_libraries(test_mkl PRIVATE trigdx Catch2::Catch2WithMain) +endif() + +include(CTest) +add_test(NAME test_lookup COMMAND test_lookup) + +if(USE_MKL) + add_test(NAME test_mkl COMMAND test_mkl) +endif() diff --git a/tests/test_lookup.cpp b/tests/test_lookup.cpp new file mode 100644 index 0000000..b647c32 --- /dev/null +++ b/tests/test_lookup.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "test_utils.hpp" + +TEST_CASE("sinf") { + test_sinf>(1e-2f); + test_sinf>(1e-2f); +} + +TEST_CASE("cosf") { + test_cosf>(1e-2f); + test_cosf>(1e-2f); +} + +TEST_CASE("sincosf") { + test_sincosf>(1e-2f); + test_sincosf>(1e-2f); +} \ No newline at end of file diff --git a/tests/test_mkl.cpp b/tests/test_mkl.cpp new file mode 100644 index 0000000..664300d --- /dev/null +++ b/tests/test_mkl.cpp @@ -0,0 +1,10 @@ +#include +#include + +#include "test_utils.hpp" + +TEST_CASE("sinf") { test_sinf(1e-6f); } + +TEST_CASE("cosf") { test_cosf(1e-6f); } + +TEST_CASE("sincosf") { test_sincosf(1e-6f); } diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp new file mode 100644 index 0000000..dc64c4e --- /dev/null +++ b/tests/test_utils.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +#include +#include + +const size_t N = 1e7; + +template inline void test_sinf(float tol) { + std::vector x(N), s_ref(N), s(N); + + for (size_t i = 0; i < N; ++i) { + x[i] = float(i) * 0.01f; + } + + ReferenceBackend ref; + Backend backend; + backend.init(); + + ref.compute_sinf(N, x.data(), s_ref.data()); + backend.compute_sinf(N, x.data(), s.data()); + + for (size_t i = 0; i < N; ++i) { + REQUIRE_THAT(s[i], Catch::Matchers::WithinAbs(s_ref[i], tol)); + } +} + +template inline void test_cosf(float tol) { + std::vector x(N), c_ref(N), c(N); + + for (size_t i = 0; i < N; ++i) { + x[i] = float(i) * 0.01f; + } + + ReferenceBackend ref; + Backend backend; + backend.init(); + + ref.compute_cosf(N, x.data(), c_ref.data()); + backend.compute_cosf(N, x.data(), c.data()); + + for (size_t i = 0; i < N; ++i) { + REQUIRE_THAT(c[i], Catch::Matchers::WithinAbs(c_ref[i], tol)); + } +} + +template inline void test_sincosf(float tol) { + std::vector x(N), s_ref(N), c_ref(N), s(N), c(N); + + for (size_t i = 0; i < N; ++i) { + x[i] = float(i) * 0.01f; + } + + ReferenceBackend ref; + Backend backend; + backend.init(); + + ref.compute_sincosf(N, x.data(), s_ref.data(), c_ref.data()); + backend.compute_sincosf(N, x.data(), s.data(), c.data()); + + for (size_t i = 0; i < N; ++i) { + REQUIRE_THAT(s[i], Catch::Matchers::WithinAbs(s_ref[i], tol)); + REQUIRE_THAT(c[i], Catch::Matchers::WithinAbs(c_ref[i], tol)); + } +}