最近开始研究OpenCL,以便在需要大量计算时,用GPU来加速。
为了实用性,结合工作,编写第一个OpenCL的程序。
为了实用,我选择以前做的数字滤波来作为本次的设计目标。我们的数字滤波,主要是用FIR滤波,需要滤波的数据是多通道的电生理数据,滤波系数是使 用Matlab仿真的,原始数据是通过多道生理记录仪采集的数据。以前的滤波程序是在CPU上完成的。现在把它移到GPU上完成,就当是一次实验和学习 OpenCL的机会。
第一步,下载CUDA(因为我用的是NVidia的显卡,如果使用ATI的显卡,需要下载AMD APP(其前身是 ATI Stream),目前版本2.7,可到网上搜索下载)
CUDA 下载 (版本4.2)http://developer.nvidia.com/cuda/cuda-downloads
下载完后安装。安装就不介绍了。注意安装的路径,下面要用到。
第二步 VS2008设置
工具->选项
项目->属性
可能你的安装目录跟我的不一样,设置成你的安装目录就可以了。
其它设置可以在网上查。或者 百度文库
http://wenku.baidu.com/view/cb77e4926bec0975f465e238.html
第三步,开始编程。
为了实用和可重用,做成一个C++类。这个类必须包含3个函数,InitCL初始化, Uninit释放, Filter_GPU滤波,Filter_GPU可能会反复调用。
首先是初始化CL
-
- BOOL CFilter::InitCL()
- {
- if(m_bInitCL)
- {
- return FALSE;
- }
- cl_int iError = 0;
-
- iError = clGetPlatformIDs(1,&m_clPlatform_id,NULL);
- if (iError != CL_SUCCESS)
- {
- TRACE("Error getting platform id \n");
- exit(iError);
- }
-
- iError = clGetDeviceIDs(m_clPlatform_id, CL_DEVICE_TYPE_GPU, 1, &m_clDevice_id, NULL);
- if (iError != CL_SUCCESS)
- {
- TRACE("Error getting device ids \n");
- exit(iError);
- }
-
- m_clContext = clCreateContext(0, 1, &m_clDevice_id, NULL, NULL, &iError);
- if (iError != CL_SUCCESS)
- {
- TRACE("Error creating context \n");
- exit(iError);
- }
-
- m_clQueue = clCreateCommandQueue(m_clContext, m_clDevice_id, 0, &iError);
- if (iError != CL_SUCCESS)
- {
- TRACE("Error creating command queue \n");
- exit(iError);
- }
- }
上面的代码没什么特殊的,照着写就行了
- CString strPath;
- GetCurrentDirectory(MAX_PATH,strPath.GetBuffer(MAX_PATH));
- strPath.ReleaseBuffer();
- CString strNewPath = strPath+ "\\testgpu.cl";
- size_t src_size = 0;
- CFileFind ff;
- if(!ff.FindFile(strNewPath))
- {
- AfxMessageBox("在当前目录没有找到OpenCL的文件testgpu.cl");
- return FALSE;
- }
- ff.Close();
-
- CFile *pFile =NULL;
- try
- {
- pFile = new CFile(strNewPath,CFile::modeRead);
- }
- catch (CException* e)
- {
- e->Delete();
- AfxMessageBox(" 打开文件testgpu.cl 出错");
- return FALSE;
- }
- int iFileLen = pFile->GetLength();
- const char* source = new char[iFileLen + 1];
- ZeroMemory((void*)source,iFileLen + 1);
- pFile->Read((void *)source,iFileLen);
- delete pFile;
- pFile = NULL;
-
-
- m_clProgram = clCreateProgramWithSource(m_clContext, 1, &source, &src_size, &iError);
-
- ASSERT(iError == CL_SUCCESS);
- delete source;
- source = NULL;
-
-
- iError = clBuildProgram(m_clProgram, 1, &m_clDevice_id, NULL, NULL, NULL);
- ASSERT(iError == CL_SUCCESS);
-
- char* build_log;
- size_t log_size;
-
- clGetProgramBuildInfo(m_clProgram, m_clDevice_id, CL_PROGRAM_BUILD_LOG
- , 0, NULL, &log_size);
- build_log = new char[log_size+1];
-
- clGetProgramBuildInfo(m_clProgram, m_clDevice_id, CL_PROGRAM_BUILD_LOG
- , log_size, build_log, NULL);
- build_log[log_size] = '\0';
- CString strLog(build_log);
- TRACE(strLog + "\n");
-
- delete build_log;
- build_log = NULL;
创建两个Kernel对应两个函数
-
- m_clKernel = clCreateKernel(m_clProgram, "Filter_GPU_Single", &iError);
-
- ASSERT(iError == CL_SUCCESS);
- m_clKernel1 = clCreateKernel(m_clProgram, "Filter_GPU_Multi", &iError);
-
- ASSERT(iError == CL_SUCCESS);
- m_bInitCL = TRUE;
- return TRUE;
释放就比较简单
- BOOL CFilter::Uninit()
- {
- if(!m_bInitCL)
- {
- return FALSE;
- }
- clReleaseKernel(m_clKernel);
- clReleaseKernel(m_clKernel1);
- clReleaseCommandQueue(m_clQueue);
- clReleaseContext(m_clContext);
- return TRUE;
- }
下面开始写滤波函数
-
- BOOL CFilter::Filter_GPU(float *pBufferIn,float *pBuferOut,const int iDataNum)
- {
- if(!m_bInitCL || !m_bInitFilter)
- {
- return FALSE;
- }
- if(pBufferIn == NULL || pBuferOut == NULL || iDataNum <=0 )
- {
- return FALSE;
- }
- cl_int iError = 0;
- const int iMem_size = sizeof(float)*m_iFilterLen;
- int iSrcLen = m_iFilterLen + iDataNum -1;
- if(m_pSrcBuffer == NULL || m_iSrcBufferLen < iSrcLen)
- {
- if(m_pSrcBuffer)
- {
- delete m_pSrcBuffer;
- m_pSrcBuffer = NULL;
- }
- m_iSrcBufferLen = iSrcLen;
- m_pSrcBuffer = new float[m_iSrcBufferLen];
- }
-
- memcpy(m_pSrcBuffer,m_pDataSave + 1,(m_iFilterLen - 1) * sizeof(float));
-
- memcpy(m_pSrcBuffer + m_iFilterLen -1,pBufferIn,iDataNum * sizeof(float));
-
- memcpy(m_pDataSave,pBufferIn + iDataNum - m_iFilterLen,m_iFilterLen * sizeof(float));
-
-
- cl_mem pSrcBuffer_CL = clCreateBuffer(m_clContext, CL_MEM_READ_ONLY
- | CL_MEM_COPY_HOST_PTR, iSrcLen * sizeof(float), m_pSrcBuffer, &iError);
- cl_mem FilterBuffer_CL = clCreateBuffer(m_clContext, CL_MEM_READ_ONLY
- | CL_MEM_COPY_HOST_PTR, iMem_size, m_pFilterBuffer, &iError);
- cl_mem pBufferOut_CL = clCreateBuffer(m_clContext, CL_MEM_WRITE_ONLY
- , iSrcLen * sizeof(float), NULL, &iError);
- const int iFilterLen = m_iFilterLen;
-
- iError = clSetKernelArg(m_clKernel, 0, sizeof(cl_mem), &pSrcBuffer_CL);
- iError |= clSetKernelArg(m_clKernel, 1, sizeof(cl_mem), &FilterBuffer_CL);
- iError |= clSetKernelArg(m_clKernel, 2, sizeof(cl_mem), &pBufferOut_CL);
- iError |= clSetKernelArg(m_clKernel, 3, sizeof(size_t), &iDataNum);
- iError |= clSetKernelArg(m_clKernel, 4, sizeof(size_t), &iFilterLen);
-
- const size_t global_ws = iDataNum;
- iError = clEnqueueNDRangeKernel(m_clQueue, m_clKernel, 1, NULL
- , &global_ws, NULL, 0, NULL, NULL);
-
- iError = clEnqueueReadBuffer(m_clQueue, pBufferOut_CL, CL_TRUE
- , 0, iDataNum * sizeof(float), pBuferOut, 0, NULL, NULL);
-
- clReleaseMemObject(pSrcBuffer_CL);
- clReleaseMemObject(FilterBuffer_CL);
- clReleaseMemObject(pBufferOut_CL);
- return TRUE;
- }
-
-
- BOOL CFilter::Filter_GPU(float *pBufferIn,float *pBuferOut
- ,const int iDataNum,const int iFrameLen,BOOL bFrameOrder)
- {
- if(!m_bInitCL || !m_bInitFilter)
- {
- return FALSE;
- }
- if(pBufferIn == NULL || pBuferOut == NULL || iDataNum <=0 || iFrameLen <=0)
- {
- return FALSE;
- }
- cl_int iError = 0;
- int i =0;
- int iSrcLen = ((m_iFilterLen - 1 ) *iFrameLen + iDataNum );
- if(m_pSrcBuffer == NULL || m_iSrcBufferLen < iSrcLen)
- {
- if(m_pSrcBuffer)
- {
- delete m_pSrcBuffer;
- m_pSrcBuffer = NULL;
- }
- m_iSrcBufferLen = iSrcLen;
- m_pSrcBuffer = new float[m_iSrcBufferLen];
- }
-
- int iFrames = iDataNum / iFrameLen;
- int iBlockLen = iFrames + m_iFilterLen - 1;
- if(bFrameOrder)
- {
- for(i =0;i< iFrameLen;i++)
- {
- memcpy(m_pSrcBuffer + i * iBlockLen,m_pDataSave + i * m_iFilterLen
- + 1,(m_iFilterLen - 1) * sizeof(float));
- }
- for(i =0;i< iFrames;i++)
- {
- for(int j=0;j< iFrameLen;j++)
- {
- m_pSrcBuffer[j * iBlockLen + m_iFilterLen - 1 + i]
- = pBufferIn[i * iFrameLen + j];
- }
- }
- for(i =0;i< iFrameLen;i++)
- {
- memcpy(m_pDataSave + i * m_iFilterLen ,m_pSrcBuffer + i * iBlockLen
- + iBlockLen - m_iFilterLen,(m_iFilterLen - 1) * sizeof(float));
- }
- }
- else
- {
- for(i =0;i< iFrameLen;i++)
- {
- memcpy(m_pSrcBuffer + i * iBlockLen,m_pDataSave + i * m_iFilterLen
- + 1,(m_iFilterLen - 1) * sizeof(float));
-
- memcpy(m_pSrcBuffer+ i * iBlockLen + m_iFilterLen - 1,pBufferIn
- + i * iFrames,iFrames * sizeof(float));
- memcpy(m_pDataSave + i * m_iFilterLen,m_pSrcBuffer + i * iBlockLen
- + iBlockLen - m_iFilterLen ,m_iFilterLen * sizeof(float));
-
- }
- }
- const int iFilter_size = sizeof(float) * m_iFilterBufferLen;
-
- cl_mem pSrcBuffer_CL = clCreateBuffer(m_clContext, CL_MEM_READ_ONLY
- | CL_MEM_COPY_HOST_PTR, iSrcLen * sizeof(float), m_pSrcBuffer, &iError);
- cl_mem FilterBuffer_CL = clCreateBuffer(m_clContext, CL_MEM_READ_ONLY
- | CL_MEM_COPY_HOST_PTR, iFilter_size, m_pFilterBuffer, &iError);
- cl_mem pBufferOut_CL = clCreateBuffer(m_clContext, CL_MEM_WRITE_ONLY
- , iSrcLen * sizeof(float), NULL, &iError);
- const int iFilter = m_iFilterLen;
- iError = clSetKernelArg(m_clKernel1, 0, sizeof(cl_mem), &pSrcBuffer_CL);
- iError |= clSetKernelArg(m_clKernel1, 1, sizeof(cl_mem), &FilterBuffer_CL);
- iError |= clSetKernelArg(m_clKernel1, 2, sizeof(cl_mem), &pBufferOut_CL);
- iError |= clSetKernelArg(m_clKernel1, 3, sizeof(size_t), &iDataNum);
- iError |= clSetKernelArg(m_clKernel1, 4, sizeof(size_t), &iFilter);
- iError |= clSetKernelArg(m_clKernel1, 5, sizeof(size_t), &iFrameLen);
-
- const size_t global_ws = iDataNum;
- iError = clEnqueueNDRangeKernel(m_clQueue, m_clKernel1, 1, NULL
- , &global_ws, NULL, 0, NULL, NULL);
-
- iError = clEnqueueReadBuffer(m_clQueue, pBufferOut_CL, CL_TRUE
- , 0, iDataNum * sizeof(float), pBuferOut, 0, NULL, NULL);
-
-
- clReleaseMemObject(pSrcBuffer_CL);
- clReleaseMemObject(FilterBuffer_CL);
- clReleaseMemObject(pBufferOut_CL);
- return TRUE;
- }
Kernel 函数 ,写在testgpu.cl文件中
- __kernel void Filter_GPU_Multi(__global const float* pSrcBuffer_CL
- ,__global const float* pFilterBuffer_CL,__global float* pBufferOut_CL
- ,const int iNum,const int iFilterNum,const int iFrameLen)
- {
- const int idx = get_global_id(0);
- if (idx < iNum)
- {
- int iBlockLen = iNum / iFrameLen;
- int iFrames = idx / iBlockLen;
- float fSum =0.0f;
- for(int i = 0;i< iFilterNum;i++)
- {
- fSum += pSrcBuffer_CL[iFrames * (iFilterNum -1 + iBlockLen )
- + idx % iBlockLen + i] * pFilterBuffer_CL[iFrames * iFilterNum + i];
- }
- pBufferOut_CL[idx] = fSum;
- }
- }
-
- __kernel void Filter_GPU_Single(__global const float* pSrcBuffer_CL
- ,__global const float* pFilterBuffer_CL,__global float* pBufferOut_CL
- ,const int iNum,const int iFilterNum)
- {
- const int idx = get_global_id(0);
- if (idx < iNum)
- {
- float fSum =0.0f;
- for(int i = 0;i< iFilterNum;i++)
- {
- fSum += pSrcBuffer_CL[idx + i] * pFilterBuffer_CL[i];
- }
- pBufferOut_CL[idx] = fSum;
- }
- }
为了验证用GPU计算的结果是否正确,还需要写CPU计算的程序,以便验证其正确性。CPU计算此处就不贴代码了,完整代码请到http://download.csdn.net/detail/iddialog/4640938 下载。
以上程序在win7 CUDA SDK 4.2 和 VC++ 2008 + SP1 编译通过。能够正常运行。运行结果GPU和CPU运算结果是一致的。由于每次计算后,m_pDataSave的内容发生变化,输出缓冲前面一段数据可能会不一样。如果要测试GPU和CPU两种方式的结果是否一样,需要每次在滤波前把m_pDataSave的内容设成一样。
如果VS2008没有SP1,需要修改stdafx.h文件
删除下面这行
- #include <afxcontrolbars.h> // 功能区和控件条的MFC 支持
-
- 添加
-
- #ifdef CWinAppEx
- #undef CWinAppEx
- #endif
- #define CWinAppEx CWinApp
结尾:
由于第一次写OpenCL程序,错误在所难免。而且对于cl的滤波函数,也没有很好的算法,也没花时间去研究。只能算是实验OpenCL的一个测试 程序。对于GPU加速的程序,需要好的算法。对于程序员来说,需要改变以前的思维习惯,以前都用单一的计算单元在做计算,即便用到多线程,也是基于任务 的,也就是说A线程做一件事,B线程做另外一件事。对于运算,一般在循环中顺序执行。而GPU由于内核多,是并发执行的,因此需要考虑其并发性和乱序执 行。如果数据量小的话,其数据IO所占的时间往往比执行所占的时间多,这时使用GPU还不如使用CPU快。因此,好的算法以及运算量大的程序,才能感觉到 GPU加速的优势。要写好OpenCL程序,以后的路还很长。(什么时候我们的程序由CPU和GPU自动调节运行就好了,对用户和程序员均不透明,就像双 核或者多核CPU一样,我们根本就不用关心程序或者说某个线程在哪个核上运行!呵呵)。
(iddialog) |