From ff8ac64d61ac846d89f975d7af88b89c7db064a8 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Wed, 12 Aug 2020 16:12:04 +0200 Subject: [PATCH 1/3] Add clamping SIMD helper --- benchmarks/BM_clamp.cpp | 52 ++++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 1 + src/sfizz/SIMDHelpers.cpp | 10 +++++++ src/sfizz/SIMDHelpers.h | 26 +++++++++++++++++ src/sfizz/simd/HelpersSSE.cpp | 31 ++++++++++++++++++++ src/sfizz/simd/HelpersSSE.h | 1 + src/sfizz/simd/HelpersScalar.h | 14 +++++++++ tests/SIMDHelpersT.cpp | 27 ++++++++++++++++++ 8 files changed, 162 insertions(+) create mode 100644 benchmarks/BM_clamp.cpp 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..0c6e981e 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -59,6 +59,7 @@ 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_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..c631a0ca 100644 --- a/src/sfizz/SIMDHelpers.cpp +++ b/src/sfizz/SIMDHelpers.cpp @@ -40,6 +40,7 @@ struct SIMDDispatch { decltype(&diffScalar) diff = &diffScalar; decltype(&meanScalar) mean = &meanScalar; decltype(&meanSquaredScalar) meanSquared = &meanSquaredScalar; + decltype(&clampAllScalar) clampAll = &clampAllScalar; private: std::array(SIMDOps::_sentinel)> simdStatus; @@ -84,6 +85,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(diff) SIMD_OP(mean) SIMD_OP(meanSquared) + SIMD_OP(clampAll) } #undef SIMD_OP } @@ -119,6 +121,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(diff) SIMD_OP(mean) SIMD_OP(meanSquared) + SIMD_OP(clampAll) } } #undef SIMD_OP @@ -159,6 +162,7 @@ void SIMDDispatch::resetStatus() setStatus(SIMDOps::mean, false); setStatus(SIMDOps::meanSquared, false); setStatus(SIMDOps::upsampling, true); + setStatus(SIMDOps::clampAll, false); } /// @@ -301,4 +305,10 @@ 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 +{ + return simdDispatch().clampAll(input, low, high, size); +} + } diff --git a/src/sfizz/SIMDHelpers.h b/src/sfizz/SIMDHelpers.h index ec27824a..17b21c88 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -59,6 +59,7 @@ enum class SIMDOps { mean, meanSquared, upsampling, + clampAll, _sentinel // }; @@ -622,4 +623,29 @@ 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 output + * @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()); +} + } // namespace sfz diff --git a/src/sfizz/simd/HelpersSSE.cpp b/src/sfizz/simd/HelpersSSE.cpp index 079d6a75..7a0e7beb 100644 --- a/src/sfizz/simd/HelpersSSE.cpp +++ b/src/sfizz/simd/HelpersSSE.cpp @@ -472,3 +472,34 @@ 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); + } +} diff --git a/src/sfizz/simd/HelpersSSE.h b/src/sfizz/simd/HelpersSSE.h index a6d5e0a5..7100ee89 100644 --- a/src/sfizz/simd/HelpersSSE.h +++ b/src/sfizz/simd/HelpersSSE.h @@ -25,3 +25,4 @@ 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; diff --git a/src/sfizz/simd/HelpersScalar.h b/src/sfizz/simd/HelpersScalar.h index ab1415a7..aa2017d1 100644 --- a/src/sfizz/simd/HelpersScalar.h +++ b/src/sfizz/simd/HelpersScalar.h @@ -186,3 +186,17 @@ 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); + } +} diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 9a7c9175..ebc94feb 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -812,3 +812,30 @@ 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) ); +} From c034fd3e304d36ab28b7208b7f0fa404b51f8284 Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Wed, 12 Aug 2020 16:54:49 +0200 Subject: [PATCH 2/3] Add an allWithin SIMD helper --- benchmarks/BM_allWithin.cpp | 70 ++++++++++++++++++++++++++++++++++ benchmarks/CMakeLists.txt | 1 + src/sfizz/SIMDHelpers.cpp | 12 +++++- src/sfizz/SIMDHelpers.h | 26 ++++++++++++- src/sfizz/simd/HelpersSSE.cpp | 41 ++++++++++++++++++++ src/sfizz/simd/HelpersSSE.h | 1 + src/sfizz/simd/HelpersScalar.h | 20 ++++++++++ tests/SIMDHelpersT.cpp | 11 ++++++ 8 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 benchmarks/BM_allWithin.cpp 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/CMakeLists.txt b/benchmarks/CMakeLists.txt index 0c6e981e..71fd0b14 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -60,6 +60,7 @@ 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 c631a0ca..c86cecf8 100644 --- a/src/sfizz/SIMDHelpers.cpp +++ b/src/sfizz/SIMDHelpers.cpp @@ -41,6 +41,7 @@ struct SIMDDispatch { decltype(&meanScalar) mean = &meanScalar; decltype(&meanSquaredScalar) meanSquared = &meanSquaredScalar; decltype(&clampAllScalar) clampAll = &clampAllScalar; + decltype(&allWithinScalar) allWithin = &allWithinScalar; private: std::array(SIMDOps::_sentinel)> simdStatus; @@ -86,6 +87,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(mean) SIMD_OP(meanSquared) SIMD_OP(clampAll) + SIMD_OP(allWithin) } #undef SIMD_OP } @@ -122,6 +124,7 @@ void SIMDDispatch::setStatus(SIMDOps op, bool enable) SIMD_OP(mean) SIMD_OP(meanSquared) SIMD_OP(clampAll) + SIMD_OP(allWithin) } } #undef SIMD_OP @@ -163,6 +166,7 @@ void SIMDDispatch::resetStatus() setStatus(SIMDOps::meanSquared, false); setStatus(SIMDOps::upsampling, true); setStatus(SIMDOps::clampAll, false); + setStatus(SIMDOps::allWithin, true); } /// @@ -308,7 +312,13 @@ void diff(const float* input, float* output, unsigned size) noexcept template <> void clampAll(float* input, float low, float high, unsigned size) noexcept { - return simdDispatch().clampAll(input, low, high, size); + 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 17b21c88..1cfa48d1 100644 --- a/src/sfizz/SIMDHelpers.h +++ b/src/sfizz/SIMDHelpers.h @@ -60,6 +60,7 @@ enum class SIMDOps { meanSquared, upsampling, clampAll, + allWithin, _sentinel // }; @@ -628,7 +629,6 @@ void diff(absl::Span input, absl::Span output) noexcept * * @tparam T the underlying type * @param input - * @param output * @param low * @param high * @param size @@ -648,4 +648,28 @@ 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 7a0e7beb..3ae8b984 100644 --- a/src/sfizz/simd/HelpersSSE.cpp +++ b/src/sfizz/simd/HelpersSSE.cpp @@ -503,3 +503,44 @@ void clampAllSSE(float* input, float low, float high, unsigned size) noexcept 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 7100ee89..cff28650 100644 --- a/src/sfizz/simd/HelpersSSE.h +++ b/src/sfizz/simd/HelpersSSE.h @@ -26,3 +26,4 @@ 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 aa2017d1..d5ac1162 100644 --- a/src/sfizz/simd/HelpersScalar.h +++ b/src/sfizz/simd/HelpersScalar.h @@ -200,3 +200,23 @@ void clampAllScalar(T* input, T low, T high, unsigned size ) noexcept 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 ebc94feb..2dbfd94b 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -839,3 +839,14 @@ TEST_CASE("[Helpers] clampAll (SIMD vs scalar)") 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) ); + sfz::setSIMDOpStatus(sfz::SIMDOps::allWithin, true); + REQUIRE( sfz::allWithin(input, 0.5f, 11.0f) ); + REQUIRE( !sfz::allWithin(input, 2.5f, 8.0f) ); +} From c629f6803b0a0aa31408858d83aa2e6417479e8f Mon Sep 17 00:00:00 2001 From: Paul Ferrand Date: Wed, 12 Aug 2020 19:40:33 +0200 Subject: [PATCH 3/3] Some more tests --- tests/SIMDHelpersT.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/SIMDHelpersT.cpp b/tests/SIMDHelpersT.cpp index 2dbfd94b..805bb0f9 100644 --- a/tests/SIMDHelpersT.cpp +++ b/tests/SIMDHelpersT.cpp @@ -846,7 +846,11 @@ TEST_CASE("[Helpers] allWithin") 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) ); }