Add SIMD helpers targeted for the mod matrix and plug them in

On my machine now the previous code path was probably vectorized
but this way we have the structure to handle platforms where it's not
This commit is contained in:
Paul Ferrand 2020-09-08 23:00:19 +02:00
parent db9840c5bd
commit 48b8011354
10 changed files with 388 additions and 5 deletions

View file

@ -0,0 +1,83 @@
// 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 <benchmark/benchmark.h>
#include <random>
#include <numeric>
#include <vector>
#include <cmath>
#include <iostream>
class MultiplyMul : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State& state) {
std::random_device rd { };
std::mt19937 gen { rd() };
std::uniform_real_distribution<float> dist { 0.1f, 1.0f };
input = std::vector<float>(state.range(0));
output = std::vector<float>(state.range(0));
gain = std::vector<float>(state.range(0));
std::fill(output.begin(), output.end(), 2.0f );
std::generate(gain.begin(), gain.end(), [&]() { return dist(gen); });
std::generate(input.begin(), input.end(), [&]() { return dist(gen); });
}
void TearDown(const ::benchmark::State& /* state */) {
}
std::vector<float> gain;
std::vector<float> input;
std::vector<float> output;
};
BENCHMARK_DEFINE_F(MultiplyMul, Straight)(benchmark::State& state) {
for (auto _ : state)
{
for (int i = 0; i < state.range(0); ++i)
output[i] *= gain[i] * input[i];
}
}
BENCHMARK_DEFINE_F(MultiplyMul, Scalar)(benchmark::State& state) {
for (auto _ : state)
{
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, false);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(output));
}
}
BENCHMARK_DEFINE_F(MultiplyMul, SIMD)(benchmark::State& state) {
for (auto _ : state)
{
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, true);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(output));
}
}
BENCHMARK_DEFINE_F(MultiplyMul, Scalar_Unaligned)(benchmark::State& state) {
for (auto _ : state)
{
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, false);
sfz::multiplyMul<float>(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1));
}
}
BENCHMARK_DEFINE_F(MultiplyMul, SIMD_Unaligned)(benchmark::State& state) {
for (auto _ : state)
{
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, true);
sfz::multiplyMul<float>(absl::MakeSpan(gain).subspan(1), absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1));
}
}
BENCHMARK_REGISTER_F(MultiplyMul, Straight)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMul, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMul, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMul, Scalar_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMul, SIMD_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN();

View file

@ -0,0 +1,88 @@
// 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 <benchmark/benchmark.h>
#include <random>
#include <numeric>
#include <vector>
#include <cmath>
#include <iostream>
class MultiplyMulFixedGain : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State& state)
{
std::random_device rd {};
std::mt19937 gen { rd() };
std::uniform_real_distribution<float> dist { 0.1f, 1.0f };
input = std::vector<float>(state.range(0));
output = std::vector<float>(state.range(0));
gain = dist(gen);
std::fill(output.begin(), output.end(), 2.0f);
std::generate(input.begin(), input.end(), [&]() { return dist(gen); });
}
void TearDown(const ::benchmark::State& /* state */)
{
}
float gain = {};
std::vector<float> input;
std::vector<float> output;
};
BENCHMARK_DEFINE_F(MultiplyMulFixedGain, Straight)
(benchmark::State& state)
{
for (auto _ : state) {
for (int i = 0; i < state.range(0); ++i)
output[i] *= gain * input[i];
}
}
BENCHMARK_DEFINE_F(MultiplyMulFixedGain, Scalar)
(benchmark::State& state)
{
for (auto _ : state) {
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, false);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(output));
}
}
BENCHMARK_DEFINE_F(MultiplyMulFixedGain, SIMD)
(benchmark::State& state)
{
for (auto _ : state) {
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, true);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(output));
}
}
BENCHMARK_DEFINE_F(MultiplyMulFixedGain, Scalar_Unaligned)
(benchmark::State& state)
{
for (auto _ : state) {
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, false);
sfz::multiplyMul1<float>(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1));
}
}
BENCHMARK_DEFINE_F(MultiplyMulFixedGain, SIMD_Unaligned)
(benchmark::State& state)
{
for (auto _ : state) {
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, true);
sfz::multiplyMul1<float>(gain, absl::MakeSpan(input).subspan(1), absl::MakeSpan(output).subspan(1));
}
}
BENCHMARK_REGISTER_F(MultiplyMulFixedGain, Straight)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMulFixedGain, Scalar)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMulFixedGain, SIMD)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMulFixedGain, Scalar_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_REGISTER_F(MultiplyMulFixedGain, SIMD_Unaligned)->RangeMultiplier(4)->Range(1 << 2, 1 << 12);
BENCHMARK_MAIN();

View file

@ -49,6 +49,8 @@ target_link_libraries(bm_ADSR PRIVATE sfizz::sfizz)
sfizz_add_benchmark(bm_add BM_add.cpp)
sfizz_add_benchmark(bm_multiplyAdd BM_multiplyAdd.cpp)
sfizz_add_benchmark(bm_multiplyAddFixedGain BM_multiplyAddFixedGain.cpp)
sfizz_add_benchmark(bm_multiplyMul BM_multiplyMul.cpp)
sfizz_add_benchmark(bm_multiplyMulFixedGain BM_multiplyMulFixedGain.cpp)
sfizz_add_benchmark(bm_subtract BM_subtract.cpp)
sfizz_add_benchmark(bm_copy BM_copy.cpp)
sfizz_add_benchmark(bm_mean BM_mean.cpp)

View file

@ -29,6 +29,8 @@ struct SIMDDispatch {
decltype(&divideScalar<T>) divide = &divideScalar<T>;
decltype(&multiplyAddScalar<T>) multiplyAdd = &multiplyAddScalar<T>;
decltype(&multiplyAdd1Scalar<T>) multiplyAdd1 = &multiplyAdd1Scalar<T>;
decltype(&multiplyMulScalar<T>) multiplyMul = &multiplyMulScalar<T>;
decltype(&multiplyMul1Scalar<T>) multiplyMul1 = &multiplyMul1Scalar<T>;
decltype(&linearRampScalar<T>) linearRamp = &linearRampScalar<T>;
decltype(&multiplicativeRampScalar<T>) multiplicativeRamp = &multiplicativeRampScalar<T>;
decltype(&addScalar<T>) add = &addScalar<T>;
@ -81,6 +83,8 @@ void SIMDDispatch<float>::setStatus(SIMDOps op, bool enable)
SIMD_OP(subtract1)
SIMD_OP(multiplyAdd)
SIMD_OP(multiplyAdd1)
SIMD_OP(multiplyMul)
SIMD_OP(multiplyMul1)
SIMD_OP(copy)
SIMD_OP(cumsum)
SIMD_OP(diff)
@ -118,6 +122,8 @@ void SIMDDispatch<float>::setStatus(SIMDOps op, bool enable)
SIMD_OP(subtract1)
SIMD_OP(multiplyAdd)
SIMD_OP(multiplyAdd1)
SIMD_OP(multiplyMul)
SIMD_OP(multiplyMul1)
SIMD_OP(copy)
SIMD_OP(cumsum)
SIMD_OP(diff)
@ -158,6 +164,8 @@ void SIMDDispatch<float>::resetStatus()
setStatus(SIMDOps::subtract1, false);
setStatus(SIMDOps::multiplyAdd, false);
setStatus(SIMDOps::multiplyAdd1, false);
setStatus(SIMDOps::multiplyMul, false);
setStatus(SIMDOps::multiplyMul1, false);
setStatus(SIMDOps::copy, false);
setStatus(SIMDOps::cumsum, true);
setStatus(SIMDOps::diff, false);
@ -243,6 +251,18 @@ void multiplyAdd1<float>(float gain, const float* input, float* output, unsigned
return simdDispatch<float>().multiplyAdd1(gain, input, output, size);
}
template <>
void multiplyMul<float>(const float* gain, const float* input, float* output, unsigned size) noexcept
{
return simdDispatch<float>().multiplyMul(gain, input, output, size);
}
template <>
void multiplyMul1<float>(float gain, const float* input, float* output, unsigned size) noexcept
{
return simdDispatch<float>().multiplyMul1(gain, input, output, size);
}
template <>
float linearRamp<float>(float* output, float start, float step, unsigned size) noexcept
{

View file

@ -52,6 +52,8 @@ enum class SIMDOps {
subtract1,
multiplyAdd,
multiplyAdd1,
multiplyMul,
multiplyMul1,
copy,
cumsum,
diff,
@ -322,6 +324,56 @@ void multiplyAdd1(T gain, absl::Span<const T> input, absl::Span<T> output) noexc
multiplyAdd1<T>(gain, input.data(), output.data(), minSpanSize(input, output));
}
/**
* @brief Applies a gain to the input and multiply the output with it
*
* @tparam T the underlying type
* @param gain
* @param input
* @param output
* @param size
*/
template <class T>
void multiplyMul(const T* gain, const T* input, T* output, unsigned size) noexcept
{
multiplyMulScalar(gain, input, output, size);
}
template <>
void multiplyMul<float>(const float* gain, const float* input, float* output, unsigned size) noexcept;
template <class T>
void multiplyMul(absl::Span<const T> gain, absl::Span<const T> input, absl::Span<T> output) noexcept
{
CHECK_SPAN_SIZES(gain, input, output);
multiplyMul<T>(gain.data(), input.data(), output.data(), minSpanSize(gain, input, output));
}
/**
* @brief Applies a fixed gain to the input and multiply the output with it
*
* @tparam T the underlying type
* @param gain
* @param input
* @param output
* @param size
*/
template <class T>
void multiplyMul1(T gain, const T* input, T* output, unsigned size) noexcept
{
multiplyMul1Scalar(gain, input, output, size);
}
template <>
void multiplyMul1<float>(float gain, const float* input, float* output, unsigned size) noexcept;
template <class T>
void multiplyMul1(T gain, absl::Span<const T> input, absl::Span<T> output) noexcept
{
CHECK_SPAN_SIZES(input, output);
multiplyMul1<T>(gain, input.data(), output.data(), minSpanSize(input, output));
}
/**
* @brief Compute a linear ramp blockwise between 2 values
*

View file

@ -355,16 +355,14 @@ float* ModMatrix::getModulation(TargetId targetId)
}
else {
if (targetFlags & kModIsMultiplicative) {
for (uint32_t i = 0; i < numFrames; ++i)
buffer[i] *= sourceDepth * sourceBuffer[i];
multiplyMul1<float>(sourceDepth, sourceBuffer, buffer);
}
else if (targetFlags & kModIsPercentMultiplicative) {
for (uint32_t i = 0; i < numFrames; ++i)
buffer[i] *= (0.01f * sourceDepth) * sourceBuffer[i];
multiplyMul1<float>(0.01f * sourceDepth, sourceBuffer, buffer);
}
else {
ASSERT(targetFlags & kModIsAdditive);
sfz::multiplyAdd1<float>(sourceDepth, sourceBuffer, buffer);
multiplyAdd1<float>(sourceDepth, sourceBuffer, buffer);
}
}
}

View file

@ -183,6 +183,49 @@ void multiplyAdd1SSE(float gain, const float* input, float* output, unsigned siz
*output++ += gain * (*input++);
}
void multiplyMulSSE(const float* gain, const float* input, float* output, unsigned size) noexcept
{
const auto sentinel = output + size;
#if SFIZZ_HAVE_SSE2
const auto* lastAligned = prevAligned<ByteAlignment>(sentinel);
while (unaligned<ByteAlignment>(input, output) && output < lastAligned)
*output++ *= (*gain++) * (*input++);
while (output < lastAligned) {
auto mmOut = _mm_load_ps(output);
mmOut = _mm_mul_ps(_mm_mul_ps(_mm_load_ps(gain), _mm_load_ps(input)), mmOut);
_mm_store_ps(output, mmOut);
incrementAll<TypeAlignment>(gain, input, output);
}
#endif
while (output < sentinel)
*output++ *= (*gain++) * (*input++);
}
void multiplyMul1SSE(float gain, const float* input, float* output, unsigned size) noexcept
{
const auto sentinel = output + size;
#if SFIZZ_HAVE_SSE2
const auto* lastAligned = prevAligned<ByteAlignment>(sentinel);
while (unaligned<ByteAlignment>(input, output) && output < lastAligned)
*output++ *= gain * (*input++);
auto mmGain = _mm_set1_ps(gain);
while (output < lastAligned) {
auto mmOut = _mm_load_ps(output);
mmOut = _mm_mul_ps(_mm_mul_ps(mmGain, _mm_load_ps(input)), mmOut);
_mm_store_ps(output, mmOut);
incrementAll<TypeAlignment>(input, output);
}
#endif
while (output < sentinel)
*output++ *= gain * (*input++);
}
float linearRampSSE(float* output, float start, float step, unsigned size) noexcept
{
const auto sentinel = output + size;

View file

@ -14,6 +14,8 @@ void gain1SSE(float gain, const float* input, float* output, unsigned size) noex
void divideSSE(const float* input, const float* divisor, float* output, unsigned size) noexcept;
void multiplyAddSSE(const float* gain, const float* input, float* output, unsigned size) noexcept;
void multiplyAdd1SSE(float gain, const float* input, float* output, unsigned size) noexcept;
void multiplyMulSSE(const float* gain, const float* input, float* output, unsigned size) noexcept;
void multiplyMul1SSE(float gain, const float* input, float* output, unsigned size) noexcept;
float linearRampSSE(float* output, float start, float step, unsigned size) noexcept;
float multiplicativeRampSSE(float* output, float start, float step, unsigned size) noexcept;
void addSSE(const float* input, float* output, unsigned size) noexcept;

View file

@ -67,6 +67,22 @@ inline void multiplyAdd1Scalar(T gain, const T* input, T* output, unsigned size)
*output++ += gain * (*input++);
}
template <class T>
inline void multiplyMulScalar(const T* gain, const T* input, T* output, unsigned size) noexcept
{
const auto sentinel = output + size;
while (output < sentinel)
*output++ *= (*gain++) * (*input++);
}
template <class T>
inline void multiplyMul1Scalar(T gain, const T* input, T* output, unsigned size) noexcept
{
const auto sentinel = output + size;
while (output < sentinel)
*output++ *= gain * (*input++);
}
template <class T>
T linearRampScalar(T* output, T start, T step, unsigned size) noexcept
{

View file

@ -517,6 +517,7 @@ TEST_CASE("[Helpers] MultiplyAdd (SIMD)")
REQUIRE(output == expected);
}
TEST_CASE("[Helpers] MultiplyAdd (SIMD vs scalar)")
{
std::vector<float> gain(bigBufferSize);
@ -574,6 +575,84 @@ TEST_CASE("[Helpers] MultiplyAdd fixed gain (SIMD vs scalar)")
REQUIRE(approxEqual<float>(outputScalar, outputSIMD));
}
TEST_CASE("[Helpers] MultiplyMul (Scalar)")
{
std::array<float, 5> gain { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f };
std::array<float, 5> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
std::array<float, 5> output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f };
std::array<float, 5> expected { 0.0f, 0.8f, 1.8f, 2.4f, 2.0f };
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, true);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
}
TEST_CASE("[Helpers] MultiplyMul (SIMD)")
{
std::array<float, 5> gain { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f };
std::array<float, 5> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
std::array<float, 5> output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f };
std::array<float, 5> expected { 0.0f, 0.8f, 1.8f, 2.4f, 2.0f };
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, true);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(output));
REQUIRE(approxEqual<float>(output, expected));
}
TEST_CASE("[Helpers] MultiplyMul (SIMD vs Scalar)")
{
std::vector<float> gain(bigBufferSize);
std::vector<float> input(bigBufferSize);
std::vector<float> outputScalar(bigBufferSize);
std::vector<float> outputSIMD(bigBufferSize);
absl::c_iota(gain, 0.0f);
absl::c_iota(input, 0.0f);
absl::c_iota(outputScalar, 0.0f);
absl::c_iota(outputSIMD, 0.0f);
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, false);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(outputScalar));
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul, true);
sfz::multiplyMul<float>(gain, input, absl::MakeSpan(outputSIMD));
REQUIRE(approxEqual<float>(outputScalar, outputSIMD));
}
TEST_CASE("[Helpers] MultiplyMul fixed gain (Scalar)")
{
float gain = 0.3f;
std::array<float, 5> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
std::array<float, 5> output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f };
std::array<float, 5> expected { 1.5f, 2.4f, 2.7f, 2.4f, 1.5f };
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, false);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(output));
REQUIRE(output == expected);
}
TEST_CASE("[Helpers] MultiplyMul fixed gain (SIMD)")
{
float gain = 0.3f;
std::array<float, 5> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
std::array<float, 5> output { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f };
std::array<float, 5> expected { 1.5f, 2.4f, 2.7f, 2.4f, 1.5f };
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, true);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(output));
REQUIRE(output == expected);
}
TEST_CASE("[Helpers] MultiplyMul fixed gain (SIMD vs scalar)")
{
float gain = 0.3f;
std::vector<float> input(bigBufferSize);
std::vector<float> outputScalar(bigBufferSize);
std::vector<float> outputSIMD(bigBufferSize);
absl::c_iota(input, 0.0f);
absl::c_iota(outputScalar, 0.0f);
absl::c_iota(outputSIMD, 0.0f);
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, false);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(outputScalar));
sfz::setSIMDOpStatus<float>(sfz::SIMDOps::multiplyMul1, true);
sfz::multiplyMul1<float>(gain, input, absl::MakeSpan(outputSIMD));
REQUIRE(approxEqual<float>(outputScalar, outputSIMD));
}
TEST_CASE("[Helpers] Subtract")
{
std::array<float, 5> input { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };