C 在英特尔MKL中,为什么FFT的计算时间比元素对元素的乘法要短?

C 在英特尔MKL中,为什么FFT的计算时间比元素对元素的乘法要短?,c,fft,intel-mkl,C,Fft,Intel Mkl,我有1024*4608个元素的向量(原始信号),它存储在一维数组中 我通过将每1024个元素复制32次到1024*32*4608,将原始_信号放大到扩展_信号 然后我使用1024*32的Com_数组与Expand_信号进行元素到元素的乘法,并对乘法后的数组进行1024FFT 核心代码如下: //initialize Original_signal MKL_Complex8 *Original_signal = new MKL_Complex8[1024*4608]; for (int i=0;

我有1024*4608个元素的向量(原始信号),它存储在一维数组中

我通过将每1024个元素复制32次到1024*32*4608,将原始_信号放大到扩展_信号

然后我使用1024*32的Com_数组Expand_信号进行元素到元素的乘法,并对乘法后的数组进行1024FFT

核心代码如下:

//initialize Original_signal
MKL_Complex8 *Original_signal = new MKL_Complex8[1024*4608];
for (int i=0; i<4608; i++)
{
  for (int j=0; j<1024; j++)
    {
      Original_signal[j+i*1024].real=rand();
      Original_signal[j+i*1024].imag=rand();
    }
 }
//Com_array
MKL_Complex8 *Com_array= new MKL_Complex8[32*1024];
for (int i=0; i<32; i++)
  {
    for (int j=0; j<1024; j++)
      {
        Com_array[j+i*1024].real=cosf(2*pi*(i-16.0)/10.0*j^2);
        Com_array[j+i*1024].imag=sinf(2*pi*(i-16.0)/10.0*j^2);
      }
  }


//element-to-element multiplication
MKL_Complex8 *Temp_signal= new MKL_Complex8[1024*32];
MKL_Complex8 *Expand_signal= new MKL_Complex8[1024*32*4608];

gettimeofday(&Bgn_Time, 0);

for (int i=0; i<4608; i++)
  {
    for (int j=0; j<32; j++)
      {
        memcpy(Temp_signal+j*1024, Original_signal+i*1024, 1024*sizeof(MKL_Complex8));
      }
      vmcMul(1024*32, Temp_signal, Com_array, Expand_signal+i*1024*32);
  }

gettimeofday(&End_Time, 0);
double time_used = (double)(End_Time.tv_sec-Bgn_Time.tv_sec)*1000000+(double)(End_Time.tv_usec-Bgn_Time.tv_usec);
printf("element-to-element multiplication use time %fus\n, time_used ");


//FFT
DFTI_DESCRIPTOR_HANDLE h_FFT = 0;
DftiCreateDescriptor(&h_FFT, DFTI_SINGLE, DFTI_COMPLEX, 1, 1024);
DftiSetValue(h_FFT, DFTI_NUMBER_OF_TRANSFORMS, 32*4608);
DftiSetValue(h_FFT, DFTI_INPUT_DISTANCE, 1024);
DftiCommitDescriptor(h_FFT);


gettimeofday(&Bgn_Time, 0);

DftiComputeForward(h_FFT,Expand_signal);

gettimeofday(&End_Time, 0);
double time_used = (double)(End_Time.tv_sec-Bgn_Time.tv_sec)*1000000+(double)(End_Time.tv_usec-Bgn_Time.tv_usec);
printf("FFT use time %fus\n, time_used ");
//初始化原始\u信号
MKL_Complex8*原始信号=新的MKL_Complex8[1024*4608];
对于(int i=0;i
在这个项目中,N=1024。理论上,FFT比元素到元素的乘法慢5倍。为什么在实际中快

如注释中所示,FFT的时间复杂度为您提供了各种FFT长度的相对度量,最高可达某个常数因子。当尝试与其他计算进行比较时,该因子变得非常重要。此外,您的分析假设性能受到浮点运算的限制,而实际执行NCE似乎受到其他因素的限制,如特殊情况处理(例如,
NaN
Inf
),内存和缓存访问

有没有办法加快这个项目

由于性能瓶颈是围绕复杂的元素向量乘法运算,下面将重点介绍如何提高该运算的性能

我没有MKL来执行实际的基准测试,但可以公平地假设
vmcMul
实现对特殊情况(如
NaN
Inf
)非常健壮,并且在这种情况下进行了相当优化

如果您不需要在SSE3处理器上运行的针对特殊情况的健壮性,可以保证阵列大小是2的倍数,并且它们是16字节对齐的,那么您可以通过使用简化的实现来获得一些性能增益,如以下(基于的):

与将
vmcMul
替换为
packed_vec_mult
的实现相比,最后一步将使您的性能提高约20%


最后,由于循环在独立块上执行操作,因此您可能能够获得显著更高的吞吐量(但延迟类似)通过在多个线程上启动并行计算,使CPU始终处于忙碌状态,而不是等待数据从内存传输到内存。我的测试表明大约有2倍的改进,但结果可能因具体机器而异。

对于元素mult,您的计时可能包括大量I/Oi应用程序有2N次读取。对于FFT,它是N次读取。在FFT情况下,函数调用开销也较少。您可能还需要检查CPU/核心调度计划,以查看是否有许多FFT是并行进行的,以及vcMul是否也是并行进行的。通常也就是说,一个算法的时间复杂度为N,另一个算法的时间复杂度为N logn并不是说这些数字是可比较的。在这两种情况下,都有一个常数因子(C1*N vs C2*N*log(N))在每种情况下都是不同的。插入排序是O(N*N),而快速排序是O(N logn)-但是对于短列表,插入排序通常更快,因为(隐含的)常数较小。欢迎使用Stackoverflow!是否为使用特定值?其中一些模式执行错误检查或提高精度。这些功能可能导致计算速度较慢。长度为N的dft的浮点计数约为5Nlog_2(N)(//Cooley Tukey算法)。对于N=1024,它粗略地对应于每个值50次浮点运算。对于实信号,它可以被2除。它确实比1次乘法大得多!@francis I在vmcMul中专门使用了VML_EP模式,它可以加快乘法速度并降低精度。但时间开销仍然比FFT大。@Sleuthye I将此线程绑定在o中只有一个CPU核心,所以我认为它们在同一个环境中。
#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 https://stackoverflow.com/questions/3211346/complex-mul-and-div-using-sse-instructions#4884057
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;
  }
}
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;
  }
}