一、FFT介绍

  傅里叶变换是数字信号处理领域一个很重要的数学变换,它用来实现将信号从时域到频域的变换,在物理学、数论、组合数学、信号处理、概率、统计、密码学、声学、光学等领域有广泛的应用。离散傅里叶变换(Discrete Fourier Transform,DFT)是连续傅里叶变换在离散系统中的表示形式,由于DFT的计算量很大,因此在很长一段时间内其应用受到了很大的限制。20世纪60年代(1965年)由Cooley和Tukey提出了快速傅里叶变换(Fast Fourier Transform,FFT)算法,它是DFT的快速算法,使得离散傅里叶变换和卷积这类难度很大的计算工作的复杂度从N2量级降到了Nlog2N量级,大大提高了DFT的运算速度,从而使DFT在实际应用中得到了广泛的应用。

  传统上,GPU只负责图形渲染,而大部分的处理都交给了CPU。自二十世纪九十年代开始,GPU的发展迅速。由于GPU具有强大的并行计算能力,加之其可编程能力的不断提高,GPU也用于通用计算,为科学计算的应用提供了新的选择。

  2007年6月,NVIDIA公司推出了CUDA (Compute Unified Device Architecture),CUDA 不需要借助图形学API,而是采用了类C语言进行开发。同时,CUDA采用了统一处理架构,降低了编程的难度,同时,NVIDIA GPU引入了片内共享存储器,提高了效率。这两项改进使CUDA架构更加适合进行GPU通用计算。

二、快速傅里叶变换(FFT)

快速傅里叶变换(英语:Fast Fourier Transform, FFT),是快速计算序列的离散傅里叶变换(DFT)或其逆变换的方法[1]。傅里叶分析将信号从原始域(通常是时间或空间)转换到頻域的表示或者逆过来转换。

FFT会通过把DFT矩阵分解为稀疏(大多为零)因子之积来快速计算此类变换。因此,它能够将计算DFT的复杂度从只用DFT定义计算需要的 O(n2),降低到O(nlog n),其中n为数据大小。

快速傅里叶变换广泛的应用于工程、科学和数学领域。这里的基本思想在1965年才得到普及,但早在1805年就已推导出来。1994年美國數學家吉尔伯特·斯特朗把FFT描述为“我们一生中最重要的数值算法”,它还被IEEE科学与工程计算期刊列入20世纪十大算法。

三、FFT的CPU实现

一维FFT基2算法的实现

我们使用按频率抽取的方法实现了一维FFT基2算法。算法的关键代码如下:

(1) 声明双精度复数的结构体:

struct Complex
{
double re; //复数的实部
double im; //复数的虚部
};

(2) 通过幂数获得快速傅里叶变换的长度,并初始化:

count =  << power; //power为幂数,count为快速傅里叶变换的长度
a = new Complex[count]; //a为初始化的数组,用来存放时域复数的值
b = new Complex[count]; //b为变换后存放结果的数组
memcpy(a, t, sizeof(Complex)*count); //初始化,将时域数据存放在a中,t为时域数据

(3) 计算旋转因子:

w = new Complex[count / ];
for (i = ; i<count / ; i++)
{
angle = -i*pi * / count;
w[i].re = cos(angle);
w[i].im = sin(angle);
}

(4) 采用频率分解法进行蝶形运算:

for (k = ; k<power; k++)
{
for (j = ; j< << k; j++)
{
bfsize = << (power - k);
for (i = ; i<bfsize / ; i++)
{
p = j*bfsize;
b[i + p] = Add(a[i + p], a[i + p + bfsize / ]); //Add()函数实现复数的加法
b[i + p + bfsize / ] = Mul(Sub(a[i + p], a[i + p + bfsize / ]), w[i*( << k)]);//Mul()函数实现复数的乘法,Sub()函数实现复数的减法
}
}
x = a;
a = b;
b = x;
}

(5) 蝶形运算全部完成后,对结果进行整序,从而得到正确的输出顺序:

for (j = ; j<count; j++)
{
p = ;
for (i = ; i<power; i++)
{
if (j&( << i))
p += << (power - i - );
}
f[j] = a[p];
}

二维FFT基2算法的实现

通过两次调用一维快速傅里叶变换即可实现二维快速傅里叶变换。关键代码如下:

(1) 计算进行傅里叶变换的宽度、高度,以及垂直方向上、水平方向上的迭代次数:

while (w *  <= width)  //计算进行傅立叶变换的宽度(2的整数次方)
{
w *= ; //w为变换的宽度
wp++; //wp为垂直方向上的迭代次数
}
while (h * <= height)//计算进行傅立叶变换的高度(2的整数次方)
{
h *= ; //h为变换的高度
hp++; //hp为水平方向上的迭代次数
}

(2) 两次调用一维快速傅里叶变换:

for (j = ; j<h; j++)    //在垂直方向上进行快速傅立叶变换
{
FFT1D(&t[w*j], &f[w*j], wp);
}
for (j = ; j<h; j++) //转换变换结果
{
for (i = ; i<w; i++)
{
t[j + h*i] = f[i + w*j];
}
}
for (j = ; j<w; j++) //水平方向进行快速傅立叶变换
{
FFT1D(&t[j*h], &f[j*h], hp);
}

四、FFT的GPU实现

  对一维或多维信号进行离散傅里叶变换的FFT变换自身具有可“分治”实现的特点,因此能高效地在GPU平台上实现。CUDA提供了封装好的CUFFT库,它提供了与CPU上的FFTW库相似的接口,能够让使用者轻易地挖掘GPU的强大浮点处理能力,又不用自己去实现专门的FFT内核函数。使用者通过调用CUFFT库的API函数,即可完成FFT变换。

  常见的FFT库在功能上有很多不同。有些库采用了基2变换,只能处理长度为2的指数的FFT,而其他的一些可以处理任意长度的FFT。CUFFT4.0提供了以下功能:

(1) 对实数或复数进行一维、二维或三维离散傅里叶变换;

(2) 可以同时并行处理一批一维离散傅里叶变换;

(3) 对任意维的离散傅里叶变换,单精度最大长度可达到6400万,双精度最大长度可达到12800万(实际长度受限于GPU存储器的可用大小);

(4) 对实数或复数进行的FFT,结果输出位置可以和输入位置相同(原地变换),也可以不同;

(5) 可在兼容硬件(GT200以及之后的GPU)上运行双精度的变换;

(6)支持流执行:数据传输过程中可以同时执行计算过程。

一维FFT算法的CUDA实现

关键代码如下:

#include <cufft.h>          //CUFFT文件头
#define NX 1024
#define BATCH 1 cufftDoubleComplex *data; //显存数据指针 //在显存中分配空间
cudaMalloc((void**)&data, sizeof(cufftDoubleComplex)*NX*BATCH); //创建CUFFT句柄
cufftHandle plan;
cufftPlan1d(&plan, NX, CUFFT_Z2Z, BATCH); //执行CUFFT
cufftExecZ2Z(plan, data, data, CUFFT_FORWARD); //快速傅里叶正变换 //销毁句柄,并释放空间
cufftDestroy(plan);
cudaFree(data);

二维FFT算法的CUDA实现

二维FFT算法实现中,同一维FFT不同的是:

(1) 输入参数:没有BATCH,增加了NY。NX为行数,NY为列数;

(2) FFT的正变换的输入位置和输出位置不同。

关键代码如下:

#include <cufft.h>          //CUFFT文件头

#define NX 1024
#define NY 1024 cufftDoubleComplex *idata, *odata; //显存数据指针 //在显存中分配空间
cudaMalloc((void**)&idata, sizeof(cufftDoubleComplex)*NX*NY);
cudaMalloc((void**)&odata, sizeof(cufftDoubleComplex)*NX*NY); //创建CUFFT句柄
cufftHandle plan;
cufftPlan2d(&plan, NX, NY, CUFFT_Z2Z); //执行CUFFT
cufftExecZ2Z(plan, idata, odata, CUFFT_FORWARD); //快速傅里叶正变换 //销毁句柄,并释放空间
cufftDestroy(plan);
cudaFree(idata);
cudaFree(odata);

三维FFT算法的CUDA实现

#define NX 64
#define NY 64
#define NZ 128 cufftHandle plan;
cufftComplex *data1, *data2;
cudaMalloc((void**)&data1, sizeof(cufftComplex)*NX*NY*NZ);
cudaMalloc((void**)&data2, sizeof(cufftComplex)*NX*NY*NZ);
/* Create a 3D FFT plan. */
cufftPlan3d(&plan, NX, NY, NZ, CUFFT_C2C); /* Transform the first signal in place. */
cufftExecC2C(plan, data1, data1, CUFFT_FORWARD); /* Transform the second signal using the same plan. */
cufftExecC2C(plan, data2, data2, CUFFT_FORWARD); /* Destroy the cuFFT plan. */
cufftDestroy(plan);
cudaFree(data1); cudaFree(data2);

五、实例

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// Include CUDA runtime and CUFFT
#include <cuda_runtime.h>
#include <cufft.h> // Helper functions for CUDA
#include "device_launch_parameters.h" #define pi 3.1415926535
#define LENGTH 100 //signal sampling points
int main()
{
// data gen
float Data[LENGTH] = { ,,, };
float fs = 1000000.000;//sampling frequency
float f0 = 200000.00;// signal frequency
for (int i = ; i < LENGTH; i++)
{
Data[i] = 1.35*cos( * pi*f0*i / fs);//signal gen,
} cufftComplex *CompData = (cufftComplex*)malloc(LENGTH * sizeof(cufftComplex));//allocate memory for the data in host
int i;
for (i = ; i < LENGTH; i++)
{
CompData[i].x = Data[i];
CompData[i].y = ;
} cufftComplex *d_fftData;
cudaMalloc((void**)&d_fftData, LENGTH * sizeof(cufftComplex));// allocate memory for the data in device
cudaMemcpy(d_fftData, CompData, LENGTH * sizeof(cufftComplex), cudaMemcpyHostToDevice);// copy data from host to device cufftHandle plan;// cuda library function handle
cufftPlan1d(&plan, LENGTH, CUFFT_C2C, );//declaration
cufftExecC2C(plan, (cufftComplex*)d_fftData, (cufftComplex*)d_fftData, CUFFT_FORWARD);//execute
cudaDeviceSynchronize();//wait to be done
cudaMemcpy(CompData, d_fftData, LENGTH * sizeof(cufftComplex), cudaMemcpyDeviceToHost);// copy the result from device to host for (i = ; i < LENGTH / ; i++)
{
printf("i=%d\tf= %6.1fHz\tRealAmp=%3.1f\t", i, fs*i / LENGTH, CompData[i].x*2.0 / LENGTH);
printf("ImagAmp=+%3.1fi", CompData[i].y*2.0 / LENGTH);
printf("\n");
}
cufftDestroy(plan);
free(CompData);
cudaFree(d_fftData); }

CUDA学习笔记3:CUFFT(CUDA提供了封装好的CUFFT库)的使用例子的更多相关文章

  1. CUDA学习笔记2:CUDA(英伟达显卡统一计算架构)与已有的VS项目结合

    一.步骤 1.先新建一个简单的控制台应用程序,项目名称为Mytest,如下图所示: 2.在项目中添加一个名为Test.cu文件,如下图所示: 3.在解决方案资源管理器中选择该项目并点击右键,在弹出的菜 ...

  2. CUDA学习笔记4:CUDA(英伟达显卡统一计算架构)代码运行时间测试

    CUDA内核运行时间的测量函数 cudaEvent_t start1; cudaEventCreate(&start1); cudaEvent_t stop1; cudaEventCreate ...

  3. 并发编程学习笔记(4)----jdk5中提供的原子类及Lock使用及原理

    (1)jdk中原子类的使用: jdk5中提供了很多原子类,它会使变量的操作变成原子性的. 原子性:原子性指的是一个操作是不可中断的,即使是在多个线程一起操作的情况下,一个操作一旦开始,就不会被其他线程 ...

  4. Directx11学习笔记【二】 将HelloWin封装成类

    我们把上一个教程的代码封装到一个类中来方便以后的使用. 首先新建一个空工程叫做MyHelloWin,添加一个main.cpp文件,然后新建一个类叫做MyWindow,将于窗体有关的操作封装到里面 My ...

  5. CUDA学习笔记(二)【转】

    来源:http://luofl1992.is-programmer.com/posts/38847.html 编程语言的特点是要实践,实践多了才有经验.很多东西书本上讲得不慎清楚,不妨自己用代码实现一 ...

  6. CUDA学习笔记1

    最近要做三维重建就学习一下cuda的一些使用. CUDA并行变成的基本四路是把一个很大的任务划分成N个简单重复的操作,创建N个线程分别执行. CPU和GPU,有各自的存储空间: Host, CPU a ...

  7. 算法学习笔记——sort 和 qsort 提供的快速排序

    这里存放的是笔者在学习算法和数据结构时相关的学习笔记,记录了笔者通过网络和书籍资料中学习到的知识点和技巧,在供自己学习和反思的同时为有需要的人提供一定的思路和帮助. 从排序开始 基本的排序算法包括冒泡 ...

  8. 学习笔记:安装swig+用SWIG封装C++为Python模块+SWIG使用说明

    这段时间一直在摸索swing,用它来封装C++代码来生成python脚步语言.并总结了swing从安装到配置再到代码封装编译生成动态库的整个过程,下面这篇文章都是我在实际的运用中的一些经验总结,分享给 ...

  9. CUDA学习笔记(三)——CUDA内存

    转自:http://blog.sina.com.cn/s/blog_48b9e1f90100fm5f.html 结合lec07_intro_cuda.pptx学习 内存类型 CGMA: Compute ...

随机推荐

  1. BestCoder Round92

    题目链接:传送门 HDU 6015-6018 解题报告:传送门 HDU6015 Skip the Class  Accepts: 678  Submissions: 1285  Time Limit: ...

  2. css3全屏背景显示

    background:url(zhongyi2.png) no-repeat center center fixed;/* -webkit-background-size:cover; -moz-ba ...

  3. JavaScript 核心

    我们首先来看一下对象[Object]的概念,这也是 ECMASript 中最基本的概念. 对象 Object ECMAScript 是一门高度抽象的面向对象(object-oriented)语言,用以 ...

  4. N-gram语言模型与马尔科夫假设关系(转)

    1.从独立性假设到联合概率链朴素贝叶斯中使用的独立性假设为 P(x1,x2,x3,...,xn)=P(x1)P(x2)P(x3)...P(xn) 去掉独立性假设,有下面这个恒等式,即联合概率链规则 P ...

  5. 【swupdate文档 五】从可信的来源更新镜像

    从可信的来源更新镜像 现在越来越重要的是,设备不仅要能安全地进行更新操作, 而且要能够验证发送的图像是否来自一个已知的源, 并且没有嵌入恶意软件. 为了实现这个目标,SWUpdate必须验证传入的镜像 ...

  6. C++ Class与Struct的区别

    转自某楼层的回复http://bbs.csdn.net/topics/280085643 首先,讨论这个问题应该仅从语法上讨论,如果讨论不同人之间编程风格上的差异,那这个问题是没有答案的.毕竟不同的人 ...

  7. MySQL删除数据几种情况以及是否释放磁盘空间【转】

    MySQL删除数据几种情况以及是否释放磁盘空间: 1.drop table table_name 立刻释放磁盘空间 ,不管是 Innodb和MyISAM ; 2.truncate table tabl ...

  8. HTML标签学习之路-001

    1.html的注释 <!--这里是注释内容--> <!--代表注释内容的开始 -->代表注释内容结束 注释部分,不会被浏览器输出,只是作为代码的说明,供开发者查阅 2.HTML ...

  9. Spring Boot学习——单元测试

    本随笔记录使用Spring Boot进行单元测试,主要是Service和API(Controller)进行单元测试. 一.Service单元测试 选择要测试的service类的方法,使用idea自动创 ...

  10. javaScript传递参数,参数变化问题

    值传递 var a=10; b(a); function b(v){ v--; } alert(a); //out 10 对象传递 var a={}; a.v=10; b(a); function b ...