【问题标题】:Why is cuFFT so slow?为什么 cuFFT 这么慢?
【发布时间】:2013-08-06 19:13:15
【问题描述】:

我希望加速计算机视觉应用程序,该应用程序在 Intel CPU 上使用 FFTW 和 OpenMP 计算许多 FFT。但是,对于各种 FFT 问题大小,我发现 cuFFT 比使用 OpenMP 的 FFTW 慢。

在下面的实验和讨论中,我发现对于批量 2D FFT,cuFFT比 FFTW 慢为什么 cuFFT 这么慢,有什么办法可以让 cuFFT 运行得更快?


实验 (code download)

我们的computer vision application 需要在一堆大小为 256x256 的小平面上进行前向 FFT。我在深度为 32 的 HOG 功能上运行 FFT,因此我使用批处理模式对每个函数调用执行 32 个 FFT。通常,我执行大约 8 个大小为 256x256 的 FFT 函数调用,批处理大小为 32。

FFTW + OpenMP
以下代码在Intel i7-2600 8-core CPU 上以 16.0ms 的时间执行。

int depth = 32; int nRows = 256; int nCols = 256; int nIter = 8;
int n[2] = {nRows, nCols};

//if nCols is even, cols_padded = (nCols+2). if nCols is odd, cols_padded = (nCols+1)
int cols_padded = 2*(nCols/2 + 1); //allocate this width, but tell FFTW that it's nCols width
int inembed[2] = {nRows, 2*(nCols/2 + 1)};
int onembed[2] = {nRows, (nCols/2 + 1)}; //default -- equivalent ot onembed=NULL

float* h_in = (float*)malloc(sizeof(float)*nRows*cols_padded*depth);
memset(h_in, 0, sizeof(float)*nRows*cols_padded*depth);
fftwf_complex* h_freq = reinterpret_cast<fftwf_complex*>(h_in); //in-place version

fftwf_plan forwardPlan = fftwf_plan_many_dft_r2c(2, //rank
                                                 n, //dims -- this doesn't include zero-padding
                                                 depth, //howmany
                                                 h_in, //in
                                                 inembed, //inembed
                                                 depth, //istride
                                                 1, //idist
                                                 h_freq, //out
                                                 onembed, //onembed
                                                 depth, //ostride
                                                 1, //odist
                                                 FFTW_PATIENT /*flags*/);
double start = read_timer();
#pragma omp parallel for
for(int i=0; i<nIter; i++){
    fftwf_execute_dft_r2c(forwardPlan, h_in, h_freq);
}
double responseTime = read_timer() - start;
printf("did %d FFT calls in %f ms \n", nIter, responseTime);


cuFFT
以下代码在顶级NVIDIA K20 GPU 上以21.7ms 执行。请注意,即使我使用流,cuFFT does not run multiple FFTs concurrently

int depth = 32; int nRows = 256; int nCols = 256; int nIter = 8;
int n[2] = {nRows, nCols};

int cols_padded = 2*(nCols/2 + 1); //allocate this width, but tell FFTW that it's nCols width
int inembed[2] = {nRows, 2*(nCols/2 + 1)};
int onembed[2] = {nRows, (nCols/2 + 1)}; //default -- equivalent ot onembed=NULL in FFTW
cufftHandle forwardPlan;
float* d_in; cufftComplex* d_freq;
CHECK_CUFFT(cufftPlanMany(&forwardPlan,
              2, //rank
              n, //dimensions = {nRows, nCols}
              inembed, //inembed
              depth, //istride
              1, //idist
              onembed, //onembed
              depth, //ostride
              1, //odist
              CUFFT_R2C, //cufftType
              depth /*batch*/));

CHECK_CUDART(cudaMalloc(&d_in, sizeof(float)*nRows*cols_padded*depth));
d_freq = reinterpret_cast<cufftComplex*>(d_in);

double start = read_timer();
for(int i=0; i<nIter; i++){

    CHECK_CUFFT(cufftExecR2C(forwardPlan, d_in, d_freq));
}
CHECK_CUDART(cudaDeviceSynchronize());
double responseTime = read_timer() - start;
printf("did %d FFT calls in %f ms \n", nIter, responseTime);

其他说明

  • 在 GPU 版本中,CPU 和 GPU 之间的cudaMemcpys不包括在我的计算时间中。
  • 此处显示的性能数字是多个实验的平均值,其中每个实验有 8 个 FFT 函数调用(总共 10 个实验,因此有 80 个 FFT 函数调用)。
  • 我尝试了许多问题大小(例如 128x128、256x256、512x512、1024x1024),深度都为 32。基于nvvp 分析器,某些尺寸(如 1024x1024)能够使 GPU 完全饱和。但是,对于所有这些大小,CPU FFTW+OpenMP 比 cuFFT 更快。

【问题讨论】:

  • 如果您让 omp 版本在单独的数据区域上工作,而不是全部敲击相同的 h_inh_freq 变量(这可能不明智,即使用于测试目的),会发生什么。在某种程度上,您似乎已经回答了自己的问题。对于 256x256 的情况,您似乎在说 GPU 没有被充分利用。您可能应该测试大型案例。
  • 我对@9​​87654333@ 和h_freq 的看法是,在您的实际工作中,您不会对相同的数据执行8 次相同的fft 操作。如果在omp parallel for 中添加private 指令以要求每个线程处理h_inh_freq 的私有副本,或者更好地制作8 个数据集h_in[0..7]h_freq[0..7],会发生什么情况。出于测试目的,这对我来说似乎更明智。否则所有 8 个线程都在处理相同的数据集(输入和输出),这有点奇怪。
  • 这是一个不公平的比较。 nIter 的 8 次迭代在 CPU 上并行运行,而在 GPU 上顺序运行。
  • @solvingPuzzles 如果 GPU 没有饱和(并且在您正在测试的大小上,我更关心内存的限制而不是性能),那么只需增加批量大小以适应 nIter好吧。
  • 我终于开始尝试“用更少的函数调用和更大的批处理 (depth &gt;&gt; 32) 来做 GPU 工作”实验。令人惊讶的是,这并没有帮助。我还没有找到 GPU 比 CPU 快的上述基准测试配置(对于任何问题规模)。谁能找到 GPU 更快的 any 配置(使用depth&gt;=32)? ...另外,罗伯特,我尝试了您使用更现实的数据结构的建议。大致相同的性能。

标签: cuda computer-vision gpu fft fftw


【解决方案1】:

问题可能已经过时,尽管这是一个可能的解释(对于 cuFFT 的缓慢性)。

在为cufftPlanMany 构建数据时,GPU 的数据排列不是很好。实际上,使用 32 的跨度和跨度意味着没有数据读取被合并。有关读取模式的详细信息,请参阅here

input[b * idist + (x * inembed[1] + y) * istride]
output[b * odist + (x * onembed[1] + y) * ostride]

在这种情况下,如果 i/ostride 为 32,则它不太可能是合并/最优的。 (确实b 是批号)。以下是我应用的更改:

    CHECK_CUFFT(cufftPlanMany(&forwardPlan,
              2, //rank
              n, //dimensions = {nRows, nCols}
              inembed, //inembed
              1,  // WAS: depth, //istride
              nRows*cols_padded, // WAS: 1, //idist
              onembed, //onembed
              1, // WAS: depth, //ostride
              nRows*cols_padded, // WAS:1, //odist
              CUFFT_R2C, //cufftType
              depth /*batch*/));

运行此程序,由于非法内存访问,我进入了未指定的启动失败。您可能想要更改内存分配(cufftComplex 是两个浮点数,您的分配大小需要 x2 - 看起来像是错字)。

// WAS : CHECK_CUDART(cudaMalloc(&d_in, sizeof(float)*nRows*cols_padded*depth)); 
CHECK_CUDART(cudaMalloc(&d_in, sizeof(float)*nRows*cols_padded*depth*2)); 

以这种方式运行时,我的卡性能得到了 x8 的提升。

【讨论】:

    猜你喜欢
    • 2016-03-22
    • 2021-09-03
    • 2016-09-28
    • 2020-02-08
    • 2012-07-17
    • 2011-11-07
    • 2015-08-24
    • 2014-07-16
    • 2011-01-02
    相关资源
    最近更新 更多