diff --git a/benchmarks/BM_allWithin.cpp b/benchmarks/BM_allWithin.cpp new file mode 100644 index 00000000..c1b3bec0 --- /dev/null +++ b/benchmarks/BM_allWithin.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include "Macros.h" +#include +#include +#include +#include +#include +#include + +class WithinArray : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 1, 10 }; + input = std::vector(state.range(0)); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state) { + UNUSED(state); + } + + std::vector input; +}; + +BENCHMARK_DEFINE_F(WithinArray, ScalarFalse)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, false); + sfz::allWithin(input, 1.2f, 3.8f); + } +} + +BENCHMARK_DEFINE_F(WithinArray, SIMDFalse)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, true); + sfz::allWithin(input, 1.2f, 3.8f); + } +} + +BENCHMARK_DEFINE_F(WithinArray, ScalarTrue)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, false); + sfz::allWithin(input, 0.0f, 11.0f); + } +} + +BENCHMARK_DEFINE_F(WithinArray, SIMDTrue)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, true); + sfz::allWithin(input, 0.0f, 11.0f); + } +} + + +BENCHMARK_REGISTER_F(WithinArray, ScalarFalse)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(WithinArray, SIMDFalse)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(WithinArray, ScalarTrue)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(WithinArray, SIMDTrue)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); diff --git a/benchmarks/BM_clamp.cpp b/benchmarks/BM_clamp.cpp new file mode 100644 index 00000000..beb66ae5 --- /dev/null +++ b/benchmarks/BM_clamp.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SIMDHelpers.h" +#include "Macros.h" +#include +#include +#include +#include +#include +#include + +class ClampArray : public benchmark::Fixture { +public: + void SetUp(const ::benchmark::State& state) { + std::random_device rd { }; + std::mt19937 gen { rd() }; + std::uniform_real_distribution dist { 0, 10 }; + input = std::vector(state.range(0)); + std::generate(input.begin(), input.end(), [&]() { return dist(gen); }); + } + + void TearDown(const ::benchmark::State& state) { + UNUSED(state); + } + + std::vector input; +}; + +BENCHMARK_DEFINE_F(ClampArray, Scalar)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, false); + sfz::clampAll(absl::MakeSpan(input), 1.2f, 3.8f); + } +} + +BENCHMARK_DEFINE_F(ClampArray, SIMD)(benchmark::State& state) { + for (auto _ : state) + { + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, true); + sfz::clampAll(absl::MakeSpan(input), 1.2f, 3.8f); + } +} + + +BENCHMARK_REGISTER_F(ClampArray, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_REGISTER_F(ClampArray, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12); +BENCHMARK_MAIN(); diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index cb38feb6..71fd0b14 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -59,6 +59,8 @@ sfizz_add_benchmark(bm_maps BM_maps.cpp) target_link_libraries(bm_maps PRIVATE absl::flat_hash_map) sfizz_add_benchmark(bm_mapVsArray BM_mapVsArray.cpp) sfizz_add_benchmark(bm_random BM_random.cpp) +sfizz_add_benchmark(bm_clamp BM_clamp.cpp) +sfizz_add_benchmark(bm_allWithin BM_allWithin.cpp) sfizz_add_benchmark(bm_logger BM_logger.cpp) target_link_libraries(bm_logger PRIVATE sfizz::sfizz) diff --git a/src/sfizz/SIMDHelpers.cpp b/src/sfizz/SIMDHelpers.cpp index c0e21d4e..c86cecf8 100644 --- a/src/sfizz/SIMDHelpers.cpp +++ b/src/sfizz/SIMDHelpers.cpp @@ -40,6 +40,8 @@ struct SIMDDispatch { decltype(&diffScalar) diff = &diffScalar; decltype(&meanScalar) mean = &meanScalar; decltype(&meanSquaredScalar) meanSquared = &meanSquaredScalar; + decltype(&clampAllScalar) clampAll = &clampAllScalar; + decltype(&allWithinScalar) allWithin = &allWithinScalar; private: std::array(SIMDOps::_sentinel)> simdStatus; @@ -84,6 +86,8 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(diff) SIMD_OP(mean) SIMD_OP(meanSquared) + SIMD_OP(clampAll) + SIMD_OP(allWithin) } #undef SIMD_OP } @@ -119,6 +123,8 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(diff) SIMD_OP(mean) SIMD_OP(meanSquared) + SIMD_OP(clampAll) + SIMD_OP(allWithin) } } #undef SIMD_OP @@ -159,6 +165,8 @@ void SIMDDispatch::resetStatus() setStatus(SIMDOps::mean, false); setStatus(SIMDOps::meanSquared, false); setStatus(SIMDOps::upsampling, true); + setStatus(SIMDOps::clampAll, false); + setStatus(SIMDOps::allWithin, true); } /// @@ -301,4 +309,16 @@ void diff(const float* input, float* output, unsigned size) noexcept return simdDispatch().diff(input, output, size); } +template <> +void clampAll(float* input, float low, float high, unsigned size) noexcept +{ + simdDispatch().clampAll(input, low, high, size); +} + +template <> +bool allWithin(const float* input, float low, float high, unsigned size) noexcept +{ + return simdDispatch().allWithin(input, low, high, size); +} + } diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index ec27824a..1cfa48d1 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -59,6 +59,8 @@ enum class SIMDOps { mean, meanSquared, upsampling, + clampAll, + allWithin, _sentinel // }; @@ -622,4 +624,52 @@ void diff(absl::Span input, absl::Span output) noexcept diff(input.data(), output.data(), minSpanSize(input, output)); } +/** + * @brief Clamp a vector between a low and high bound + * + * @tparam T the underlying type + * @param input + * @param low + * @param high + * @param size + */ +template +void clampAll(T* input, T low, T high, unsigned size) noexcept +{ + clampAllScalar(input, low, high, size); +} + +template <> +void clampAll(float* input, float low, float high, unsigned size) noexcept; + +template +void clampAll(absl::Span input, T low, T high) noexcept +{ + clampAll(input.data(), low, high, input.size()); +} + +/** + * @brief Check that all values are within bounds (inclusive) + * + * @tparam T the underlying type + * @param input + * @param low + * @param high + * @param size + */ +template +bool allWithin(const T* input, T low, T high, unsigned size) noexcept +{ + return allWithinScalar(input, low, high, size); +} + +template <> +bool allWithin(const float* input, float low, float high, unsigned size) noexcept; + +template +bool allWithin(absl::Span input, T low, T high) noexcept +{ + return allWithin(input.data(), low, high, input.size()); +} + } // namespace sfz diff --git a/src/sfizz/simd/HelpersSSE.cpp b/src/sfizz/simd/HelpersSSE.cpp index 079d6a75..3ae8b984 100644 --- a/src/sfizz/simd/HelpersSSE.cpp +++ b/src/sfizz/simd/HelpersSSE.cpp @@ -472,3 +472,75 @@ void diffSSE(const float* input, float* output, unsigned size) noexcept incrementAll(input, output); } } + +void clampAllSSE(float* input, float low, float high, unsigned size) noexcept +{ + if (size == 0) + return; + + const auto sentinel = input + size; + +#if SFIZZ_HAVE_SSE2 + const auto* lastAligned = prevAligned(sentinel); + while (unaligned(input) && input < lastAligned){ + const float clampedAbove = *input > high ? high : *input; + *input = clampedAbove < low ? low : clampedAbove; + incrementAll(input); + } + + const auto mmLow = _mm_set1_ps(low); + const auto mmHigh = _mm_set1_ps(high); + while (input < lastAligned) { + const auto mmIn = _mm_load_ps(input); + _mm_store_ps(input, _mm_max_ps(_mm_min_ps(mmIn, mmHigh), mmLow)); + incrementAll(input); + } +#endif + + while (input < sentinel) { + const float clampedAbove = *input > high ? high : *input; + *input = clampedAbove < low ? low : clampedAbove; + incrementAll(input); + } +} + +bool allWithinSSE(const float* input, float low, float high, unsigned size) noexcept +{ + if (size == 0) + return true; + + if (low > high) + std::swap(low, high); + + const auto sentinel = input + size; + +#if SFIZZ_HAVE_SSE2 + const auto* lastAligned = prevAligned(sentinel); + while (unaligned(input) && input < lastAligned){ + if (*input < low || *input > high) + return false; + + incrementAll(input); + } + + const auto mmLow = _mm_set1_ps(low); + const auto mmHigh = _mm_set1_ps(high); + while (input < lastAligned) { + const auto mmIn = _mm_load_ps(input); + const auto mmOutside = _mm_or_ps(_mm_cmplt_ps(mmIn, mmLow), _mm_cmpgt_ps(mmIn, mmHigh)); + if (_mm_movemask_ps(mmOutside) != 0) + return false; + + incrementAll(input); + } +#endif + + while (input < sentinel) { + if (*input < low || *input > high) + return false; + + incrementAll(input); + } + + return true; +} diff --git a/src/sfizz/simd/HelpersSSE.h b/src/sfizz/simd/HelpersSSE.h index a6d5e0a5..cff28650 100644 --- a/src/sfizz/simd/HelpersSSE.h +++ b/src/sfizz/simd/HelpersSSE.h @@ -25,3 +25,5 @@ float meanSSE(const float* vector, unsigned size) noexcept; float meanSquaredSSE(const float* vector, unsigned size) noexcept; void cumsumSSE(const float* input, float* output, unsigned size) noexcept; void diffSSE(const float* input, float* output, unsigned size) noexcept; +void clampAllSSE(float* input, float low, float high, unsigned size) noexcept; +bool allWithinSSE(const float* input, float low, float high, unsigned size) noexcept; diff --git a/src/sfizz/simd/HelpersScalar.h b/src/sfizz/simd/HelpersScalar.h index ab1415a7..d5ac1162 100644 --- a/src/sfizz/simd/HelpersScalar.h +++ b/src/sfizz/simd/HelpersScalar.h @@ -186,3 +186,37 @@ void diffScalar(const T* input, T* output, unsigned size) noexcept incrementAll(input, output); } } + +template +void clampAllScalar(T* input, T low, T high, unsigned size ) noexcept +{ + if (size == 0) + return; + + const auto sentinel = input + size; + while (input < sentinel) { + const float clampedAbove = *input > high ? high : *input; + *input = clampedAbove < low ? low : clampedAbove; + incrementAll(input); + } +} + +template +bool allWithinScalar(const T* input, T low, T high, unsigned size ) noexcept +{ + if (size == 0) + return true; + + if (low > high) + std::swap(low, high); + + const auto sentinel = input + size; + while (input < sentinel) { + if (*input < low || *input > high) + return false; + + incrementAll(input); + } + + return true; +} diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 9a7c9175..805bb0f9 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -812,3 +812,45 @@ TEST_CASE("[Helpers] Width Scalar") REQUIRE(right[0] == Approx(1.0f).margin(0.001f)); } } + +TEST_CASE("[Helpers] clampAll") +{ + std::array inputScalar { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; + std::array inputSIMD; + sfz::copy(inputScalar, absl::MakeSpan(inputSIMD)); + std::array expected { 2.5f, 2.5f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 8.0f, 8.0f }; + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, false); + sfz::clampAll(absl::MakeSpan(inputScalar), 2.5f, 8.0f); + REQUIRE( approxEqual(inputScalar, expected) ); + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, true); + sfz::clampAll(absl::MakeSpan(inputSIMD), 2.5f, 8.0f); + REQUIRE( approxEqual(inputSIMD, expected) ); +} + +TEST_CASE("[Helpers] clampAll (SIMD vs scalar)") +{ + std::vector inputScalar(medBufferSize); + std::vector inputSIMD(medBufferSize); + absl::c_iota(inputScalar, 2.0f); + sfz::copy(inputScalar, absl::MakeSpan(inputSIMD)); + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, false); + sfz::clampAll(absl::MakeSpan(inputScalar), 10.0f, 50.0f); + sfz::setSIMDOpStatus(sfz::SIMDOps::clampAll, true); + sfz::clampAll(absl::MakeSpan(inputSIMD), 10.0f, 50.0f); + REQUIRE( approxEqual(inputScalar, inputSIMD) ); +} + +TEST_CASE("[Helpers] allWithin") +{ + std::array input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, false); + REQUIRE( sfz::allWithin(input, 0.5f, 11.0f) ); + REQUIRE( !sfz::allWithin(input, 2.5f, 8.0f) ); + REQUIRE( !sfz::allWithin(input, 0.0f, 5.0f) ); + REQUIRE( !sfz::allWithin(input, -1.0f, 7.0f) ); + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, true); + REQUIRE( sfz::allWithin(input, 0.5f, 11.0f) ); + REQUIRE( !sfz::allWithin(input, 2.5f, 8.0f) ); + REQUIRE( !sfz::allWithin(input, 0.0f, 5.0f) ); + REQUIRE( !sfz::allWithin(input, -1.0f, 7.0f) ); +}