HNU Comparch LAB4 用GPU加速FFT程序

一、实验目标

用GPU加速FFT程序运行,测量加速前后的运行时间,确定加速比。

二、实验要求

  • 采用CUDA或OpenCL(视具体GPU而定)编写程序
  • 根据自己的机器配置选择合适的输入数据大小 n
  • 对测量结果进行分析,确定使用GPU加速FFT程序得到的加速比
  • 回答思考题,答案加入到实验报告叙述中合适位置

三、实验内容

使用CUDA进行FFT加速

使用到的代码如下


  • FFT.h
  • typedef struct complex //复数类型
    {
    	float real;		//实部
    	float imag;		//虚部
    }complex;
    
    #define PI 3.1415926535
    
    void complex_plus(complex a, complex b, complex *c);//复数加
    void complex_mul(complex a, complex b, complex *c);//复数乘
    void complex_sub(complex a, complex b, complex *c);	//复数减法
    void complex_div(complex a, complex b, complex *c);	//复数除法
    void complex_abs(complex f[], float out[], float n);//复数数组取模
    
    void fft(int N, complex f[]);//傅立叶变换 输出也存在数组f中
    void ifft(int N, complex f[]); // 傅里叶逆变换
    
    void conjugate_complex(int n, complex in[], complex out[]);
  • CUDA.h
  • #include "FFT.h"
    #include <iostream>
    #include <time.h>
    #include "cuda_runtime.h"
    #include "device_launch_parameters.h"
    #include "../include/cufft.h"
    
    #define NX 4096 // 有效数据个数
    #define N  5335// 补0之后的数据长度
    #define MAX 1<<12
    #define BATCH 1
    #define BLOCK_SIZE 1024
    using std::cout;
    using std::endl;
    
    complex m[MAX], l[MAX];
    
    bool IsEqual(cufftComplex *idataA, cufftComplex *idataB, const long int size)
    {
    	for (int i = 0; i < size; i++)
    	{
    		if (abs(idataA[i].x - idataB[i].x) > 0.000001 || abs(idataA[i].y - idataB[i].y) > 0.000001)
    			return false;
    	}
    
    	return true;
    }
    
    
    
    /**
    * 功能:实现 cufftComplex 数组的尺度缩放,也就是乘以一个数
    * 输入:idata 输入数组的头指针
    * 输出:odata 输出数组的头指针
    * 输入:size 数组的元素个数
    * 输入:scale 缩放尺度
    */
    static __global__ void cufftComplexScale(cufftComplex *idata, cufftComplex *odata, const long int size, float scale)
    {
    	const int threadID = blockIdx.x * blockDim.x + threadIdx.x;
    
    	if (threadID < size)
    	{
    		odata[threadID].x = idata[threadID].x * scale;
    		odata[threadID].y = idata[threadID].y * scale;
    	}
    }
    
    
    void conjugate_complex(int n, complex in[], complex out[])
    {
    	int i = 0;
    	for (i = 0; i < n; i++)
    	{
    		out[i].imag = -in[i].imag;
    		out[i].real = in[i].real;
    	}
    }
    
    void complex_abs(complex f[], float out[], int n)
    {
    	int i = 0;
    	float t;
    	for (i = 0; i < n; i++)
    	{
    		t = f[i].real * f[i].real + f[i].imag * f[i].imag;
    		out[i] = sqrt(t);
    	}
    }
    
    
    void complex_plus(complex a, complex b, complex *c)
    {
    	c->real = a.real + b.real;
    	c->imag = a.imag + b.imag;
    }
    
    void complex_sub(complex a, complex b, complex *c)
    {
    	c->real = a.real - b.real;
    	c->imag = a.imag - b.imag;
    }
    
    void complex_mul(complex a, complex b, complex *c)
    {
    	c->real = a.real * b.real - a.imag * b.imag;
    	c->imag = a.real * b.imag + a.imag * b.real;
    }
    
    void complex_div(complex a, complex b, complex *c)
    {
    	c->real = (a.real * b.real + a.imag * b.imag) / (b.real * b.real + b.imag * b.imag);
    	c->imag = (a.imag * b.real - a.real * b.imag) / (b.real * b.real + b.imag * b.imag);
    }
    
    #define SWAP(a,b)  tempr=(a);(a)=(b);(b)=tempr
    
    void Wn_i(int n, int i, complex *Wn, char flag)
    {
    	Wn->real = cos(2 * PI*i / n);
    	if (flag == 1)
    		Wn->imag = -sin(2 * PI*i / n);
    	else if (flag == 0)
    		Wn->imag = -sin(2 * PI*i / n);
    }
    
    //傅里叶变化
    void fft(int NN, complex f[])
    {
    	complex t, wn;//中间变量
    	int i, j, k, m, n, l, r, M;
    	int la, lb, lc;
    	/*----计算分解的级数M=log2(N)----*/
    	for (i = NN, M = 1; (i = i / 2) != 1; M++);
    	/*----按照倒位序重新排列原信号----*/
    	for (i = 1, j = NN / 2; i <= NN - 2; i++)
    	{
    		if (i < j)
    		{
    			t = f[j];
    			f[j] = f[i];
    			f[i] = t;
    		}
    		k = NN / 2;
    		while (k <= j)
    		{
    			j = j - k;
    			k = k / 2;
    		}
    		j = j + k;
    	}
    
    	/*----FFT算法----*/
    	for (m = 1; m <= M; m++)
    	{
    		la = pow(2, m); //la=2^m代表第m级每个分组所含节点数		
    		lb = la / 2;    //lb代表第m级每个分组所含碟形单元数
    					 //同时它也表示每个碟形单元上下节点之间的距离
    		/*----碟形运算----*/
    		for (l = 1; l <= lb; l++)
    		{
    			r = (l - 1)*pow(2, M - m);
    			for (n = l - 1; n < NN - 1; n = n + la) //遍历每个分组,分组总数为N/la
    			{
    				lc = n + lb;  //n,lc分别代表一个碟形单元的上、下节点编号     
    				Wn_i(NN, r, &wn, 1);//wn=Wnr
    				complex_mul(f[lc], wn, &t);//t = f[lc] * wn复数运算
    				complex_sub(f[n], t, &(f[lc]));//f[lc] = f[n] - f[lc] * Wnr
    				complex_plus(f[n], t, &(f[n]));//f[n] = f[n] + f[lc] * Wnr
    			}
    		}
    	}
    }
    
    //傅里叶逆变换
    void ifft(int NN, complex f[])
    {
    	int i = 0;
    	conjugate_complex(NN, f, f);
    	fft(NN, f);
    	conjugate_complex(NN, f, f);
    	for (i = 0; i < NN; i++)
    	{
    		f[i].imag = (f[i].imag) / NN;
    		f[i].real = (f[i].real) / NN;
    	}
    }
    
    bool IsEqual2(complex idataA[], complex idataB[], const int size)
    {
    	for (int i = 0; i < size; i++)
    	{
    		if (abs(idataA[i].real - idataB[i].real) > 0.000001 || abs(idataA[i].imag - idataB[i].imag) > 0.000001)
    			return false;
    	}
    
    	return true;
    }
  • Main
  • int main()
    {
    	cufftComplex *data_dev; // 设备端数据头指针
    	cufftComplex *data_Host = (cufftComplex*)malloc(NX*BATCH * sizeof(cufftComplex)); // 主机端数据头指针
    	cufftComplex *resultFFT = (cufftComplex*)malloc(N*BATCH * sizeof(cufftComplex)); // 正变换的结果
    	cufftComplex *resultIFFT = (cufftComplex*)malloc(NX*BATCH * sizeof(cufftComplex)); // 先正变换后逆变换的结果
    
    	// 初始数据
    	for (int i = 0; i < NX; i++)
    	{
    		data_Host[i].x = float((rand() * rand()) % NX) / NX;
    		data_Host[i].y = float((rand() * rand()) % NX) / NX;
    	}
    
    
    	dim3 dimBlock(BLOCK_SIZE); // 线程块
    	dim3 dimGrid((NX + BLOCK_SIZE - 1) / dimBlock.x); // 线程格
    
    	cufftHandle plan; // 创建cuFFT句柄
    	cufftPlan1d(&plan, N, CUFFT_C2C, BATCH);
    
    	// 计时
    	clock_t start, stop;
    	double duration;
    	start = clock();
    
    	cudaMalloc((void**)&data_dev, sizeof(cufftComplex)*N*BATCH); // 开辟设备内存
    	cudaMemset(data_dev, 0, sizeof(cufftComplex)*N*BATCH); // 初始为0
    	cudaMemcpy(data_dev, data_Host, NX * sizeof(cufftComplex), cudaMemcpyHostToDevice); // 从主机内存拷贝到设备内存
    
    	cufftExecC2C(plan, data_dev, data_dev, CUFFT_FORWARD); // 执行 cuFFT,正变换
    	cudaMemcpy(resultFFT, data_dev, N * sizeof(cufftComplex), cudaMemcpyDeviceToHost); // 从设备内存拷贝到主机内存
    
    	cufftExecC2C(plan, data_dev, data_dev, CUFFT_INVERSE); // 执行 cuFFT,逆变换
    	cufftComplexScale << <dimGrid, dimBlock >> > (data_dev, data_dev, N, 1.0f / N); // 乘以系数
    	cudaMemcpy(resultIFFT, data_dev, NX * sizeof(cufftComplex), cudaMemcpyDeviceToHost); // 从设备内存拷贝到主机内存
    
    	stop = clock();
    	duration = (double)(stop - start) * 1000 / CLOCKS_PER_SEC;
    	cout << "FFT经过GPU加速之后的时间为" << duration << "ms" << endl;
    
    	cufftDestroy(plan); // 销毁句柄
    	cudaFree(data_dev); // 释放空间
    
    
    	if (IsEqual(data_Host, resultIFFT, NX))
    		cout << "逆变化检测通过。" << endl;
    	else
    		cout << "逆变换检测不通过。" << endl;
    
    
    
    	//cpu fft
    	for (long int i = 0; i < MAX; i++) {
    		m[i].real = float((rand() * rand()) % 4096) / 4096;
    		l[i].real = m[i].real;
    		l[i].imag = m[i].imag = 0;
    	}
    	clock_t start2, end2;
    	double duration1;
    	start2 = clock();
    	fft(MAX, m);
    	ifft(MAX, m);
    	end2 = clock();
    	duration1 = (double)(end2 - start2) * 1000 / CLOCKS_PER_SEC;
    	cout << "FFT经过CPU加速之后的时间为 " << duration1 << " ms" << endl;
    
    	if (IsEqual2(m, l, MAX))
    		cout << "逆变化检测通过。" << endl;
    	else
    		cout << "逆变化检测不通过。" << endl;
    
    	return 0;
    }

四、测试平台

  • CPU:i7-6500U

  • GPU:NVIDA GTX960M

  • 内存:DDR3 8GB

  • 操作系统:Windows 10 专业版

  • 编译器:Microsoft Visual Studio Enterprise 2017

五、测试记录

分别测试数据集2^14、2^15、2^16、2^17、2^18时的运行情况

2^14

2^15

2^16

2^17

2^18

六、实验结果分析

根据运行的时间对比,可以看出,随着数据集的增加,CPU的处理时间和数据集的增加速率基本同步(数据集增大两倍,CPU处理时间增大两倍),而GPU的处理时间基本不变。

之前在做小数据集的时候发现经过GPU的加速运行时间和CPU的运行时间不相上下,甚至比CPU还要慢。

通过分析可以知道,当数据集较小时,数据在相关寄存器/内存的传送的时间对总时间的贡献更大主要,而当数据集很大的时候,计算时间对总时间的贡献更大。为了比较性能,于是就没有放小数据集的运行情况。

可以看到,对于大数据集,经过GPU加速,计算时间基本维持在了一个常数时间(其中也有程序运行的偶然性以及计时精度的问题,实际上运行时间应该线性增加,但是速率比CPU的2倍速慢)

七、思考题

分析GPU加速FFT程序可能获得的加速比

理论上,如果GPU可以一次存储完所有的数据,那么相对于CPU,数据传送的时间可以忽略,而CPU能够一次处理的数据块的大小有限,理论的加速比应该是GPU一次可以处理的数据块大小/CPU一次可以处理的数据块大小,因为实际上GPU就是对多个数据块的并行运算。但是这个加速比不可知(需要查阅相关的数据手册,但是我没有找到)

实际加速比相对于理想加速比差多少?原因是什么?

原因:

每次运行程序时,数据在CPU/GPU和内存之间的传送时间不同(依赖于当时计算机的运行情况)

GPU上进行的类多线程方式的时间开销以及写回/验证时的可能存在的写冲突

猜你喜欢

转载自blog.csdn.net/m0_37273364/article/details/85996093