From 81be311dedc268bf2f36450597568f064aee59da Mon Sep 17 00:00:00 2001 From: Moreno Razzoli Date: Tue, 12 Oct 2021 08:59:12 +0200 Subject: [PATCH 1/6] AudioVisualWaveform, SumSamples: improve performance by 10x on x86 architecture by using platform specific optimizations --- app/audio/audiovisualwaveform.cpp | 53 +++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 5d1e86dfd..4b44dd06a 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -17,6 +17,13 @@ along with this program. If not, see . ***/ +#if defined(__x86_64__) || defined(_M_X64) +#define x86_64 +#endif + +#ifdef x86_64 +#include +#endif #include "audiovisualwaveform.h" @@ -286,17 +293,47 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples return summed_samples; } +#ifdef x86_64 +void min_max_sse(float *a, int start, int end, float &min_val, float &max_val) { + // for time being just assume that length is always mod 4 + __m128 max = _mm_loadu_ps(a + start); // load the first 4 + __m128 min = _mm_loadu_ps(a + start); // load the first 4 + for(int i = 4; i < end; i+=4) + { + __m128 cur = _mm_loadu_ps(a + start + i); + max = _mm_max_ps(max, cur); + min = _mm_min_ps(min, cur); + } + for (int i = 0; i < 3; i++) { + max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); + min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93)); + } + _mm_store_ss(&max_val, max); + _mm_store_ss(&min_val, min); +} +#endif + AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samples, int start_index, int length) { - AudioVisualWaveform::Sample summed_samples(samples->audio_params().channel_count()); + int channels = samples->audio_params().channel_count(); + AudioVisualWaveform::Sample summed_samples(channels); - int end_index = start_index + length; - - for (int i=start_index; iaudio_params().channel_count(); channel++) { - ExpandMinMax(summed_samples[channel], samples->data(channel)[i]); + min_max_sse(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max); } - } + #else + int end_index = start_index + length; + for (int channel=0; channelaudio_params().channel_count(); channel++) { + for (int i=start_index; idata(channel)[i]); + } + } + // for reference: this approximation is n x faster (and less accurate) for a n-tracks clip + // for (int i=start_index; idata(i%channels)[i]); + // } + #endif return summed_samples; } @@ -444,6 +481,10 @@ void AudioVisualWaveform::ExpandMinMax(AudioVisualWaveform::SamplePerChannel &su if (value > sum.max) { sum.max = value; } + + // to avoid branching + // sum.min = std::min(value, sum.min); + // sum.max = std::max(value, sum.max); } } From 3656ed480ed41d6a78718a723e19ae7bb79c333d Mon Sep 17 00:00:00 2001 From: Moreno Razzoli Date: Wed, 13 Oct 2021 22:58:04 +0200 Subject: [PATCH 2/6] min_max_sse: design failsafe for lenghts not mod.4 --- app/audio/audiovisualwaveform.cpp | 57 +++++++++++++++++++------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 4b44dd06a..22e169a2f 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -17,11 +17,9 @@ along with this program. If not, see . ***/ -#if defined(__x86_64__) || defined(_M_X64) -#define x86_64 -#endif +#include -#ifdef x86_64 +#ifdef Q_PROCESSOR_X86_64 #include #endif @@ -293,23 +291,38 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples return summed_samples; } -#ifdef x86_64 -void min_max_sse(float *a, int start, int end, float &min_val, float &max_val) { - // for time being just assume that length is always mod 4 - __m128 max = _mm_loadu_ps(a + start); // load the first 4 - __m128 min = _mm_loadu_ps(a + start); // load the first 4 - for(int i = 4; i < end; i+=4) - { - __m128 cur = _mm_loadu_ps(a + start + i); - max = _mm_max_ps(max, cur); - min = _mm_min_ps(min, cur); - } - for (int i = 0; i < 3; i++) { - max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); - min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93)); - } - _mm_store_ss(&max_val, max); - _mm_store_ss(&min_val, min); +#ifdef Q_PROCESSOR_X86_64 +void min_max_sse(float *a, int start, int end, float &min_val, float &max_val) +{ + // load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits) + __m128 max = _mm_loadu_ps(a + start); + __m128 min = _mm_loadu_ps(a + start); + + // loop over 'a' and compare current elements with min and max 4 by 4. + // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 + for(int i = 4; i < end-4; i+=4) { + __m128 cur = _mm_loadu_ps(a + start + i); + max = _mm_max_ps(max, cur); + min = _mm_min_ps(min, cur); + } + // so we read the last 4 (or less) elements in a safe manner. + __m128 cur = _mm_loadu_ps(a + end - 4); + max = _mm_max_ps(max, cur); + min = _mm_min_ps(min, cur); + // this potentially overlaps up to the last 3 elements but it's not an issue. + + // min and max will contain 4 min and max. To get the absolute min and max + // we need to compare the 4 values over themselves by shuffling each time. + for (int i = 0; i < 3; i++) { + max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); + min = _mm_min_ps(min, _mm_shuffle_ps(min, min, 0x93)); + } + // now min and max contain 4 identical items each representing min and max value respectively. + + // and we store the first one into a float variable. + _mm_store_ss(&max_val, max); + _mm_store_ss(&min_val, min); + // I bet you don't find annotated low level code very often. } #endif @@ -318,7 +331,7 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samp int channels = samples->audio_params().channel_count(); AudioVisualWaveform::Sample summed_samples(channels); - #ifdef x86_64 + #ifdef Q_PROCESSOR_X86_64 for (int channel=0; channelaudio_params().channel_count(); channel++) { min_max_sse(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max); } From 8a4e9922141cd6bf3471e386cd52978bc4064ede Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 13 Oct 2021 17:00:43 -0700 Subject: [PATCH 3/6] minor formatting --- app/audio/audiovisualwaveform.cpp | 28 ++++++++++++++-------------- app/node/math/math/mathbase.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/app/audio/audiovisualwaveform.cpp b/app/audio/audiovisualwaveform.cpp index 22e169a2f..1018c3e76 100644 --- a/app/audio/audiovisualwaveform.cpp +++ b/app/audio/audiovisualwaveform.cpp @@ -17,15 +17,15 @@ along with this program. If not, see . ***/ -#include - -#ifdef Q_PROCESSOR_X86_64 -#include -#endif #include "audiovisualwaveform.h" #include +#include + +#ifdef Q_PROCESSOR_X86 +#include +#endif #include "config/config.h" #include "common/functiontimer.h" @@ -291,15 +291,15 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(const float *samples return summed_samples; } -#ifdef Q_PROCESSOR_X86_64 -void min_max_sse(float *a, int start, int end, float &min_val, float &max_val) +#ifdef Q_PROCESSOR_X86 +void ExpandMinMaxSSE(float *a, int start, int end, float &min_val, float &max_val) { // load the first 4 elements of 'a' into min and max (they are 4 * 32 = 128 bits) - __m128 max = _mm_loadu_ps(a + start); + __m128 max = _mm_loadu_ps(a + start); __m128 min = _mm_loadu_ps(a + start); - // loop over 'a' and compare current elements with min and max 4 by 4. - // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 + // loop over 'a' and compare current elements with min and max 4 by 4. + // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 for(int i = 4; i < end-4; i+=4) { __m128 cur = _mm_loadu_ps(a + start + i); max = _mm_max_ps(max, cur); @@ -311,7 +311,7 @@ void min_max_sse(float *a, int start, int end, float &min_val, float &max_val) min = _mm_min_ps(min, cur); // this potentially overlaps up to the last 3 elements but it's not an issue. - // min and max will contain 4 min and max. To get the absolute min and max + // min and max will contain 4 min and max. To get the absolute min and max // we need to compare the 4 values over themselves by shuffling each time. for (int i = 0; i < 3; i++) { max = _mm_max_ps(max, _mm_shuffle_ps(max, max, 0x93)); @@ -331,11 +331,11 @@ AudioVisualWaveform::Sample AudioVisualWaveform::SumSamples(SampleBufferPtr samp int channels = samples->audio_params().channel_count(); AudioVisualWaveform::Sample summed_samples(channels); - #ifdef Q_PROCESSOR_X86_64 + #ifdef Q_PROCESSOR_X86 for (int channel=0; channelaudio_params().channel_count(); channel++) { - min_max_sse(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max); + ExpandMinMaxSSE(samples->data(channel), start_index, length, summed_samples[channel].min, summed_samples[channel].max); } - #else + #else int end_index = start_index + length; for (int channel=0; channelaudio_params().channel_count(); channel++) { for (int i=start_index; i #include +#ifdef Q_PROCESSOR_X86 +#include +#endif + #include "common/tohex.h" #include "node/distort/transform/transformdistortnode.h" #include "render/color.h" @@ -349,9 +353,32 @@ void MathNodeBase::ValueInternal(Operation operation, Pairing pairing, const QSt if (IsInputStatic(number_param)) { if (!NumberIsNoOp(operation, number)) { for (int i=0;iaudio_params().channel_count();i++) { +#ifdef Q_PROCESSOR_X86 + // Use SSE instructions for optimization + + float *a = job.samples()->data(i); + int end = job.samples()->sample_count(); + + int end_divisible_4 = (end / 4) * 4; + + // loop over 'a' and compare current elements with min and max 4 by 4. + // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 + __m128 mult = _mm_load1_ps(&number); + for(int j = 0; j < end_divisible_4; j+=4) { + __m128 cur = _mm_loadu_ps(a + j); + __m128 res = _mm_mul_ps(cur, mult); + _mm_storeu_ps(a + j, res); + } + + // Do last three numbers, if non-divisible by 4 + for (int j=end_divisible_4; jsample_count();j++) { job.samples()->data(i)[j] = PerformAll(operation, job.samples()->data(i)[j], number); } +#endif } } From 1eb5e44502b36d5a3b2f267ff570649632f1634c Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed, 13 Oct 2021 17:15:47 -0700 Subject: [PATCH 4/6] use sse instructions for volume/math --- app/node/math/math/mathbase.cpp | 60 ++++++++++++++++++++++----------- app/node/math/math/mathbase.h | 4 +++ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 3633afe2f..4723a9db9 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -167,6 +167,46 @@ void MathNodeBase::PushVector(NodeValueTable *output, olive::NodeValue::Type typ } } +#ifdef Q_PROCESSOR_X86 +void MathNodeBase::PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end) +{ + int end_divisible_4 = (end / 4) * 4; + + // loop over 'a' and compare current elements with min and max 4 by 4. + // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 + __m128 mult = _mm_load1_ps(&b); + for(int j = 0; j < end_divisible_4; j+=4) { + __m128 cur = _mm_loadu_ps(a + start + j); + __m128 res; + + // Switch statement doesn't seem to cause additional overhead, hopefully the compiler is + // unrolling the loops? + switch (operation) { + case kOpAdd: + res = _mm_add_ps(cur, mult); + break; + case kOpSubtract: + res = _mm_sub_ps(cur, mult); + break; + case kOpMultiply: + case kOpPower: // Technically incorrect but why would someone ever need this? + res = _mm_mul_ps(cur, mult); + break; + case kOpDivide: + res = _mm_div_ps(cur, mult); + break; + } + + _mm_storeu_ps(a + start + j, res); + } + + // Do last three numbers, if non-divisible by 4 + for (int j=end_divisible_4; jaudio_params().channel_count();i++) { #ifdef Q_PROCESSOR_X86 // Use SSE instructions for optimization - - float *a = job.samples()->data(i); - int end = job.samples()->sample_count(); - - int end_divisible_4 = (end / 4) * 4; - - // loop over 'a' and compare current elements with min and max 4 by 4. - // we need to make sure we don't read out of boundaries should 'a' lenght be not mod. 4 - __m128 mult = _mm_load1_ps(&number); - for(int j = 0; j < end_divisible_4; j+=4) { - __m128 cur = _mm_loadu_ps(a + j); - __m128 res = _mm_mul_ps(cur, mult); - _mm_storeu_ps(a + j, res); - } - - // Do last three numbers, if non-divisible by 4 - for (int j=end_divisible_4; jdata(i), number, 0, job.samples()->sample_count()); #else for (int j=0;jsample_count();j++) { job.samples()->data(i)[j] = PerformAll(operation, job.samples()->data(i)[j], number); diff --git a/app/node/math/math/mathbase.h b/app/node/math/math/mathbase.h index add040fe0..2a8c291a0 100644 --- a/app/node/math/math/mathbase.h +++ b/app/node/math/math/mathbase.h @@ -101,6 +101,10 @@ protected: template static T PerformAddSubMultDiv(Operation operation, T a, U b); +#ifdef Q_PROCESSOR_X86 + static void PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end); +#endif + static QString GetShaderUniformType(const NodeValue::Type& type); static QString GetShaderVariableCall(const QString& input_id, const NodeValue::Type& type, const QString &coord_op = QString()); From a064ce0c94e61628e4f47273577c7f72ed9f87fe Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu, 14 Oct 2021 00:49:08 -0700 Subject: [PATCH 5/6] mathnodebase: use func ptr instead of switch inside loop --- app/node/math/math/mathbase.cpp | 74 ++++++++++++++++++++------------- app/node/math/math/mathbase.h | 1 + 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 4723a9db9..420ad4ac9 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -167,42 +167,58 @@ void MathNodeBase::PushVector(NodeValueTable *output, olive::NodeValue::Type typ } } +void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end) +{ + for (int j=start;jdata(i), number, 0, job.samples()->sample_count()); #else - for (int j=0;jsample_count();j++) { - job.samples()->data(i)[j] = PerformAll(operation, job.samples()->data(i)[j], number); - } + PerformAllOnFloatBuffer(operation, job.samples()->data(i), number, 0, job.samples()->sample_count()); #endif } } diff --git a/app/node/math/math/mathbase.h b/app/node/math/math/mathbase.h index 2a8c291a0..940bbee21 100644 --- a/app/node/math/math/mathbase.h +++ b/app/node/math/math/mathbase.h @@ -102,6 +102,7 @@ protected: static T PerformAddSubMultDiv(Operation operation, T a, U b); #ifdef Q_PROCESSOR_X86 + static void PerformAllOnFloatBuffer(Operation operation, float *a, float b, int start, int end); static void PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end); #endif From 382d464952a6444d3bcbe7406e444ba4af0c0bb8 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat, 16 Oct 2021 10:39:49 -0700 Subject: [PATCH 6/6] math: fix linker error --- app/node/math/math/mathbase.cpp | 53 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/app/node/math/math/mathbase.cpp b/app/node/math/math/mathbase.cpp index 420ad4ac9..f488c4bb1 100644 --- a/app/node/math/math/mathbase.cpp +++ b/app/node/math/math/mathbase.cpp @@ -175,7 +175,6 @@ void MathNodeBase::PerformAllOnFloatBuffer(Operation operation, float *a, float } #ifdef Q_PROCESSOR_X86 -typedef __m128 (*SSEOperationFunction)(__m128 a, __m128 b); void MathNodeBase::PerformAllOnFloatBufferSSE(Operation operation, float *a, float b, int start, int end) { int end_divisible_4 = (end / 4) * 4; @@ -183,43 +182,37 @@ void MathNodeBase::PerformAllOnFloatBufferSSE(Operation operation, float *a, flo // Load number to multiply by into buffer __m128 mult = _mm_load1_ps(&b); - SSEOperationFunction func = nullptr; switch (operation) { case kOpAdd: - func = _mm_add_ps; - break; - case kOpSubtract: - func = _mm_sub_ps; - break; - case kOpMultiply: - func = _mm_mul_ps; - break; - case kOpDivide: - func = _mm_div_ps; - break; - case kOpPower: - // Leave as nullptr, which will fallback to non-SSE operation - break; - } - - if (func) { - // If we have an SSE version of this operation, do this now - // Loop all values for(int j = 0; j < end_divisible_4; j+=4) { - __m128 cur = _mm_loadu_ps(a + start + j); - __m128 res = func(cur, mult); - _mm_storeu_ps(a + start + j, res); + _mm_storeu_ps(a + start + j, _mm_add_ps(_mm_loadu_ps(a + start + j), mult)); } - - // Do last three numbers, if non-divisible by 4 - for (int j=end_divisible_4; j