diff --git a/CMakeLists.txt b/CMakeLists.txt index 3128d06f..ab5a2861 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,9 @@ target_link_libraries(bm_gain benchmark absl::span) add_executable(bm_looping benchmarks/BM_looping.cpp sources/SIMDSSE.cpp) target_link_libraries(bm_looping benchmark absl::span absl::algorithm) +add_executable(bm_saturating benchmarks/BM_saturating.cpp sources/SIMDSSE.cpp) +target_link_libraries(bm_saturating benchmark absl::span absl::algorithm) + add_executable(bm_ramp benchmarks/BM_ramp.cpp sources/SIMDSSE.cpp) target_link_libraries(bm_ramp benchmark absl::span absl::algorithm) diff --git a/benchmarks/BM_saturating.cpp b/benchmarks/BM_saturating.cpp new file mode 100644 index 00000000..6ede3178 --- /dev/null +++ b/benchmarks/BM_saturating.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include "../sources/SIMDHelpers.h" + +// In this one we have an array of indices + +constexpr int loopEnd { 1076 }; +constexpr float maxJump { 4 }; + +class SaturatingFixture : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, maxJump }; + indices = std::vector(state.range(0)); + leftCoeffs = std::vector(state.range(0)); + rightCoeffs = std::vector(state.range(0)); + jumps = std::vector(state.range(0)); + absl::c_generate(jumps, [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state [[maybe_unused]]) { + + } + + std::vector indices; + std::vector leftCoeffs; + std::vector rightCoeffs; + std::vector jumps; +}; + + +BENCHMARK_DEFINE_F(SaturatingFixture, Scalar)(benchmark::State& state) { + for (auto _ : state) + { + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd); + } +} + +BENCHMARK_DEFINE_F(SaturatingFixture, SIMD)(benchmark::State& state) { + for (auto _ : state) + { + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 2.5f, loopEnd); + } +} + +BENCHMARK_DEFINE_F(SaturatingFixture, Scalar_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + saturatingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd); + } +} + +BENCHMARK_DEFINE_F(SaturatingFixture, SIMD_Unaligned)(benchmark::State& state) { + for (auto _ : state) + { + saturatingSFZIndex(absl::MakeSpan(jumps).subspan(1), absl::MakeSpan(leftCoeffs).subspan(2), absl::MakeSpan(rightCoeffs).subspan(1), absl::MakeSpan(indices).subspan(3), 2.5f, loopEnd); + } +} + + +// Register the function as a benchmark +BENCHMARK_REGISTER_F(SaturatingFixture, Scalar)->RangeMultiplier(2)->Range((2<<6), (2<<12)); +BENCHMARK_REGISTER_F(SaturatingFixture, SIMD)->RangeMultiplier(2)->Range((2<<6), (2<<12)); +BENCHMARK_REGISTER_F(SaturatingFixture, Scalar_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); +BENCHMARK_REGISTER_F(SaturatingFixture, SIMD_Unaligned)->RangeMultiplier(2)->Range((2<<6), (2<<12)); +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/sources/SIMDHelpers.h b/sources/SIMDHelpers.h index be685c8b..9792d13f 100644 --- a/sources/SIMDHelpers.h +++ b/sources/SIMDHelpers.h @@ -109,6 +109,50 @@ void cos(absl::Span input, absl::Span output) noexcept template <> void cos(absl::Span input, absl::Span output) noexcept; +template <> +void cos(absl::Span input, absl::Span output) noexcept; + +template +inline void snippetSaturatingIndex(const T*& jump, T*& leftCoeff, T*& rightCoeff, int*& index, T& floatIndex, T loopEnd) +{ + floatIndex += *jump; + if (floatIndex >= loopEnd) { + floatIndex = loopEnd; + *index = static_cast(floatIndex) - 1; + *rightCoeff = static_cast(1.0); + *leftCoeff = static_cast(0.0); + } else { + *index = static_cast(floatIndex); + *rightCoeff = floatIndex - *index; + *leftCoeff = static_cast(1.0) - *rightCoeff; + } + index++; + leftCoeff++; + rightCoeff++; + jump++; +} + +template +void saturatingSFZIndex(absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs, absl::Span indices, T floatIndex, T loopEnd) noexcept +{ + ASSERT(indices.size() >= jumps.size()); + ASSERT(indices.size() == leftCoeffs.size()); + ASSERT(indices.size() == rightCoeffs.size()); + + auto* index = indices.begin(); + auto* leftCoeff = leftCoeffs.begin(); + auto* rightCoeff = rightCoeffs.begin(); + auto* jump = jumps.begin(); + const auto size = min(jumps.size(), indices.size(), leftCoeffs.size(), rightCoeffs.size()); + auto* sentinel = jumps.begin() + size; + + while (jump < sentinel) + snippetSaturatingIndex(jump, leftCoeff, rightCoeff, index, floatIndex, loopEnd); +} + +template <> +void saturatingSFZIndex(absl::Span jumps, absl::Span leftCoeffs, absl::Span rightCoeffs, absl::Span indices, float floatIndex, float loopEnd) noexcept; + template inline void snippetLoopingIndex(const T*& jump, T*& leftCoeff, T*& rightCoeff, int*& index, T& floatIndex, T loopEnd, T loopStart) { diff --git a/sources/SIMDSSE.cpp b/sources/SIMDSSE.cpp index d48c56ac..98591445 100644 --- a/sources/SIMDSSE.cpp +++ b/sources/SIMDSSE.cpp @@ -343,6 +343,63 @@ void loopingSFZIndex(absl::Span jumps, snippetLoopingIndex(jump, leftCoeff, rightCoeff, index, floatIndex, loopEnd, loopStart); } +template <> +void saturatingSFZIndex(absl::Span jumps, + absl::Span leftCoeffs, + absl::Span rightCoeffs, + absl::Span indices, + float floatIndex, + float loopEnd) noexcept +{ + ASSERT(indices.size() >= jumps.size()); + ASSERT(indices.size() == leftCoeffs.size()); + ASSERT(indices.size() == rightCoeffs.size()); + + auto index = indices.data(); + auto leftCoeff = leftCoeffs.data(); + auto rightCoeff = rightCoeffs.data(); + auto jump = jumps.data(); + const auto size = min(jumps.size(), indices.size(), leftCoeffs.size(), rightCoeffs.size()); + const auto* sentinel = jumps.begin() + size; + const auto* alignedEnd = prevAligned(sentinel); + + while (unaligned(reinterpret_cast(index), leftCoeff, rightCoeff, jump) && jump < alignedEnd) + snippetSaturatingIndex(jump, leftCoeff, rightCoeff, index, floatIndex, loopEnd); + + auto mmFloatIndex = _mm_set_ps1(floatIndex); + const auto mmLoopEnd = _mm_set1_ps(loopEnd); + const auto mmSaturated = _mm_sub_ps(mmLoopEnd, _mm_set_ps1(0.000001f)); + while (jump < alignedEnd) { + auto mmOffset = _mm_load_ps(jump); + mmOffset = _mm_add_ps(mmOffset, _mm_castsi128_ps(_mm_slli_si128(_mm_castps_si128(mmOffset), 4))); + mmOffset = _mm_add_ps(mmOffset, _mm_shuffle_ps(_mm_setzero_ps(), mmOffset, 0x40)); + + mmFloatIndex = _mm_add_ps(mmFloatIndex, mmOffset); + const auto mmCompared = _mm_cmplt_ps(mmFloatIndex, mmLoopEnd); + mmFloatIndex = _mm_add_ps(_mm_and_ps(mmCompared, mmFloatIndex), _mm_andnot_ps(mmCompared, mmSaturated)); + + auto mmIndices = _mm_cvtps_epi32(_mm_sub_ps(mmFloatIndex, _mm_set_ps1(0.4999999552965164184570312f))); + _mm_store_si128(reinterpret_cast<__m128i*>(index), mmIndices); + + auto mmRight = _mm_sub_ps(mmFloatIndex, _mm_cvtepi32_ps(mmIndices)); + auto mmLeft = _mm_sub_ps(_mm_set_ps1(1.0f), mmRight); + _mm_store_ps(leftCoeff, mmLeft); + _mm_store_ps(rightCoeff, mmRight); + + mmFloatIndex = _mm_shuffle_ps(mmFloatIndex, mmFloatIndex, _MM_SHUFFLE(3, 3, 3, 3)); + // floatingIndex = _mm_cvtss_f32(_mm_shuffle_ps(mmFloatIndex, mmFloatIndex, _MM_SHUFFLE(0, 0, 0, 3)));; + // floatingIndex = *(index + 3) + *(rightCoeff + 3); + index += TypeAlignment; + jump += TypeAlignment; + leftCoeff += TypeAlignment; + rightCoeff += TypeAlignment; + } + + floatIndex = _mm_cvtss_f32(mmFloatIndex); + while (jump < sentinel) + snippetSaturatingIndex(jump, leftCoeff, rightCoeff, index, floatIndex, loopEnd); +} + template <> float linearRamp(absl::Span output, float value, float step) noexcept { diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 005bd42d..233c1d17 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -12,6 +12,21 @@ constexpr int bigBufferSize { 4095 }; constexpr int medBufferSize { 127 }; constexpr double fillValue { 1.3 }; +template +inline bool approxEqualMargin(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) +{ + if (lhs.size() != rhs.size()) + return false; + + for (size_t i = 0; i < rhs.size(); ++i) + if (rhs[i] != Approx(lhs[i]).margin(eps)) { + std::cerr << lhs[i] << " != " << rhs[i] << " at index " << i << '\n'; + return false; + } + + return true; +} + template inline bool approxEqual(absl::Span lhs, absl::Span rhs, Type eps = 1e-3) { @@ -419,11 +434,60 @@ TEST_CASE("[Helpers] SFZ looping index (SIMD)") // std::vector rightCoeffsSIMD(bigBufferSize); // loopingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 1.0f, medBufferSize, 1); // loopingSFZIndex(jumps, absl::MakeSpan(leftCoeffsSIMD), absl::MakeSpan(rightCoeffsSIMD), absl::MakeSpan(indicesSIMD), 1.0f, medBufferSize, 1); -// REQUIRE( approxEqual(indices, indicesSIMD, 1) ); -// REQUIRE( approxEqual(leftCoeffs, leftCoeffsSIMD, 1e-2) ); -// REQUIRE( approxEqual(rightCoeffs, rightCoeffsSIMD, 1e-2) ); +// for (int i = 0; i < bigBufferSize; ++i) +// REQUIRE( ((static_cast(indices[i]) + rightCoeffs[i] == Approx(static_cast(indicesSIMD[i]) + rightCoeffsSIMD[i]).margin(1e-2)) +// || (static_cast(indices[i]) + rightCoeffs[i] == Approx(static_cast(indicesSIMD[i]) + rightCoeffsSIMD[i] - static_cast(medBufferSize)).margin(2e-2))) ); // } +TEST_CASE("[Helpers] SFZ saturating index") +{ + std::array jumps { 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f }; // 1.1 2.3 3.6 5.0 6.5 8.1 + std::array indices; + std::array leftCoeffs; + std::array rightCoeffs; + std::array expectedIndices { 2, 3, 4, 5, 5, 5 }; + std::array expectedLeft { 0.9f, 0.7f, 0.4f, 0.0f, 0.0f, 0.0f }; + std::array expectedRight { 0.1f, 0.3f, 0.6f, 1.0f, 1.0f, 1.0f }; + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 1.0f, 6); + REQUIRE(indices == expectedIndices); + REQUIRE(approxEqual(leftCoeffs, expectedLeft)); + REQUIRE(approxEqual(rightCoeffs, expectedRight)); +} + +TEST_CASE("[Helpers] SFZ saturating index (SIMD)") +{ + std::array jumps { 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f }; // 1.1 2.3 3.6 5.0 6.5 8.1 + std::array indices; + std::array leftCoeffs; + std::array rightCoeffs; + std::array expectedIndices { 2, 3, 4, 5, 5, 5 }; + std::array expectedLeft { 0.9f, 0.7f, 0.4f, 0.0f, 0.0f, 0.0f }; + std::array expectedRight { 0.1f, 0.3f, 0.6f, 1.0f, 1.0f, 1.0f }; + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 1.0f, 6); + REQUIRE(indices == expectedIndices); + REQUIRE(approxEqualMargin(leftCoeffs, expectedLeft)); + REQUIRE(approxEqualMargin(rightCoeffs, expectedRight)); +} + +TEST_CASE("[Helpers] SFZ saturating index (SIMD vs Scalar)") +{ + + std::vector jumps(medBufferSize); + absl::c_fill(jumps, fillValue); + + std::vector indices(medBufferSize); + std::vector leftCoeffs(medBufferSize); + std::vector rightCoeffs(medBufferSize); + + std::vector indicesSIMD(medBufferSize); + std::vector leftCoeffsSIMD(medBufferSize); + std::vector rightCoeffsSIMD(medBufferSize); + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffs), absl::MakeSpan(rightCoeffs), absl::MakeSpan(indices), 1.0f, 78); + saturatingSFZIndex(jumps, absl::MakeSpan(leftCoeffsSIMD), absl::MakeSpan(rightCoeffsSIMD), absl::MakeSpan(indicesSIMD), 1.0f, 78); + for (int i = 0; i < medBufferSize; ++i) + REQUIRE( static_cast(indices[i]) + rightCoeffs[i] == Approx(static_cast(indicesSIMD[i]) + rightCoeffsSIMD[i])); +} + TEST_CASE("[Helpers] Linear Ramp") { const float start { 0.0f };