为什么intel MKL中FFT的计算时间比元素到元素的乘法要短?

时间:2019-03-19 02:51:14

标签: c fft intel-mkl

我的向量为1024 * 4608个元素( Original_signal ),存储在一个一维数组中。

然后,我将每1024个元素复制32次,将 Original_signal 扩展为 Expand_signal ,到1024 * 32 * 4608。

然后,我使用1024 * 32的 Com_array Expand_signal 进行元素到元素的乘法,并执行After乘法数组的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 ");

元素到元素的乘法时间为700毫秒(除去存储器成本后),而FFT的时间为500毫秒。

FFT的复数乘法计算为N / 2log2N,元素到元素的乘法为N。

在此项目中,N = 1024。从理论上讲,FFT比元素到元素的乘法慢5倍。为什么实际上更快?

有什么方法可以加快项目进度?

(请注意 Com_array 是对称的)

1 个答案:

答案 0 :(得分:0)

  

在此项目中,N = 1024。从理论上讲,FFT比元素到元素的乘法慢5倍。为什么实际速度更快?

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

  

有什么方法可以加快项目进度?

由于您的性能瓶颈围绕着复杂的逐元素矢量乘法运算,因此以下内容将重点介绍如何提高该运算的性能。

我没有MKL可以执行实际的基准测试,但是可以合理地假设vmcMul的实现对于诸如NaNInf这样的特殊情况都相当健壮,并且在这种情况下相当优化。

如果您不需要特殊情况下的鲁棒性,请在SSE3处理器上运行,可以保证您的数组大小是2的倍数,并且它们是16字节对齐的,那么通过使用,您可能会获得一些性能提升简化的实现,例如以下内容(基于Sebastienanswer to another post):

#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;
  }
}

优化乘法后,仍然可以通过将Temp_signal与{{的不同部分)直接相乘多次来摆脱memcpyOrignal_signal的多余副本,从而改善您的实现{1}},如下所示:

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; } } 替换为vmcMul的实现相比,这最后一步将使您的性能再提高20%。

最后,由于循环在独立的块上执行操作,因此您可以通过在多个线程上启动并行计算来获得更高的吞吐量(但类似的延迟),从而使CPU始终保持繁忙状态,而不必等待数据传输往/从内存。我的测试表明,改进的程度约为2倍,但结果可能会因您的特定机器而异。