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

问题描述 投票:2回答:1

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

我通过将每1024个元素32次复制到1024 * 32 * 4608,将Original_signal放大到Expand_signal。

然后我使用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 ");

元素到元素乘法的时间是700ms(去除memcpy成本后),FFT的时间是500ms。

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

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

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

(注意Com_array是对称的)

c fft intel-mkl
1个回答
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除去额外的副本到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;
  }
}

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

最后,由于循环对独立块执行操作,您可以通过在多个线程上启动并行计算来获得更高的吞吐量(但是类似的延迟),这样CPU始终保持忙碌而不是等待传输到/来自的数据记忆。我的测试显示在2左右的改进,但结果可能会有所不同,具体取决于您的具体机器。

© www.soinside.com 2019 - 2024. All rights reserved.