В этом проекте N = 1024.БПФ в 5 раз медленнее, чем умножение элементов на элементы в теории.Почему в действительности быстрее?
Как указывалось в комментариях, временная сложность БПФ дает вам относительную меру для различных длин БПФ, вплоть до некоторого постоянного коэффициента.Этот фактор становится важным при сравнении с другими вычислениями.Кроме того, в вашем анализе предполагается, что производительность ограничена операциями с плавающей запятой, где в действительности реальная производительность кажется ограниченной другими факторами, такими как обработка в особых случаях (например, NaN
, Inf
), доступ к памяти и кэш-памяти.
Любой способ ускорить проект?
Поскольку узкое место в вашей производительности связано со сложной поэлементной операцией умножения векторов, следующие действия будут направлены на повышение производительности этой операции.
У меня нет MKL для выполнения реальных тестов, но, вероятно, справедливо предположить, что реализация vmcMul
достаточно устойчива к особым случаям, таким как NaN
и Inf
, и довольно оптимизирована вобстоятельства.
Если вам не требуется устойчивость к особым случаям, работа на процессоре SSE3, может гарантировать, что размеры вашего массива кратны 2, и что они выровнены по 16 байтов, тогда вы можетеполучить некоторый прирост производительности, используя упрощенную реализацию, такую какowing (на основе Себастьяна ответа на другой пост ):
#include <pmmintrin.h>
#include <xmmintrin.h>
// Computes and element-by-element multiplication of complex vectors "a" and "b" and
// stores the results in "c".
// Vectors "a", "b" and "c" must be:
// - vectors of even length N
// - 16-bytes aligned
// Special cases such as NaN and Inf are not handled.
//
// based on /2493757/kompleks-mul-i-div-s-ispolzovaniem-instruktsii-sse#2493768
void packed_vec_mult(int N, MKL_Complex8* a, MKL_Complex8* b, MKL_Complex8* c)
{
int M = N/2;
__m128* aptr = reinterpret_cast<__m128*>(a);
__m128* bptr = reinterpret_cast<__m128*>(b);
__m128* cptr = reinterpret_cast<__m128*>(c);
for (int i = 0; i < M; i++)
{
__m128 t0 = _mm_moveldup_ps(*aptr);
__m128 t1 = *bptr;
__m128 t2 = _mm_mul_ps(t0, t1);
__m128 t3 = _mm_shuffle_ps(t1, t1, 0xb1);
__m128 t4 = _mm_movehdup_ps(*aptr);
__m128 t5 = _mm_mul_ps(t4, t3);
*cptr = _mm_addsub_ps(t2, t5);
++aptr;
++bptr;
++cptr;
}
}
Как только умножение оптимизировано, ваша реализация может быть улучшена путем избавления отдополнительные копии в Temp_signal
с memcpy
путем прямого умножения Orignal_signal
во много раз на различные части Com_array
, как показано ниже:
MKL_Complex8* outptr = Expand_signal;
for (int i=0; i<4608; i++)
{
for (int j=0; j<32; j++)
{
packed_vec_mult(1024, Original_signal+i*1024, Com_array+j*1024, outptr);
outptr += 1024;
}
}
Этот последний шаг даст вам еще ~ 20% улучшения производительности по сравнению с вашей реализацией с заменой vmcMul
на packed_vec_mult
.
Наконец, поскольку цикл выполняет операции с независимыми блоками, вы можете получить значительно более высокую пропускную способность (но с аналогичной задержкой), запустив параллельныйвычисления в нескольких потоках, так что процессор всегда занят, а не ожидает передачи данных в / из памяти.Мои тесты показывают улучшение где-то в 2 раза, но результаты могут отличаться в зависимости от вашей конкретной машины.