赞
踩
我们身处在这个大数据时代,我们的一切无时无刻都被数据所记录,大数据甚至已经变成了另一种形式的自我个体。对于传统的计算机而言,以往很多巧妙的算法,面对海量数据也会失去光彩,变得无能为力。因为,计算机CPU总是在“单打独斗”,一个CPU的力量就算再强大,也抵不过千军万马。正如武侠小说中那样,每个时代总会有盖世英雄,但一个身怀绝技的大侠,也许面对些许敌人不足为惧,但是如果所有敌人都群拥而上,那估计他也要挂了。CPU就是一个时代中的佼佼者,他们不仅身怀绝技而且心怀天下,但是,这样的CPU总是稀少的;而GPU就不一样,虽然他们个人没有强大的能力,但是,GPU多呀,人多力量大,三个臭皮匠顶个诸葛亮。如果CPU和GPU齐心合力,便能解决这世间更多难题,CPU再也不会势单力薄了(老夫甚感欣慰),哈哈哈哈哈,扯远了~。
言归正传,本文就来讲讲如何基于CUDA编程,来利用GPU进行大量的并行计算。
CUDA(Compute Unified Device Architecture),是显卡厂商NVIDIA推出的运算平台。CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。 它包含了CUDA指令集架构(ISA)以及GPU内部的并行计算引擎。 开发人员可以使用C语言来为CUDA™架构编写程序,C语言是应用最广泛的一种高级编程语言。所编写出的程序可以在支持CUDA™的处理器上以超高性能运行。CUDA3.0已经开始支持C++和FORTRAN。
CPU把大量的空间用来放置逻辑控制和缓存的晶体管,而真正的ALU(算术逻辑单元(计算单元))却很少;GPU中用来放置控制和缓存的晶体管空间比较小,将节省下来的空间用来放置更多的轻量级计算单元(多达上万),这样GPU就有更多的计算单元可以同时进行计算。
GPU在逻辑上的组织关系:最小的单元为一个线程(Thread),多个线程(Thread)组成一个块(Block),多个块组成一个网格(Grid)
GPU在物理上的组织关系:最小的单元为一个CUDA Core(ALU,算术逻辑运算单元)又称为SP(流处理器),多个CUDA Core组成一个SM(多流处理器),多个SM组成设备Device
由CUDA中GPU在软硬件上的组织关系可以得到映射关系为:一个线程对应一个CUDA Core,一个Block对应一个SM,一个Grid对应一个Device
int main(void) { //1.确定数据分块方案,确定网格大小,块大小 //2.定义CPU和GPU端数据变量 //3.cudaMalloc函数为GPU端变量分配内存大小;malloc函数为CPU端变量分配内存大小 //4.初始化CPU端变量 //5.cudaMemcpy函数将CPU端数据变量拷贝到GPU端变量 //6.kernel函数(核函数名<<<网格配置,块配置>>>(参数变量)) //7.cudaMemcpy函数将GPU端计算结果数据拷贝到CPU端 //8.cudaFree函数释放GPU端变量内存,free释放CPU端内存 }
上面第1和第6步是设计高效CUDA程序的关键。
CUDA中CPU和GPU有各自独立的存储器,CPU通过GPU中的全局内存、常量内存和纹理内存与GPU进行数据传输,以下是CUDA进行数据传输的接口:
float *MD
int size = dataSize * sizeof(float)
cudaMalloc((void**)&MD, size)
cudaFree(MD)
cudaMemcpy(MD,MH,size,cudaMemcpyHostToDevice);//将CPU端的数据变量MH拷贝传输到GPU端变量MD上
cudaMemcpy(MH,MD,size,cudaMemcpyDeviceToHost);//将GPU端的数据变量MD拷贝传输到CPU端变量MH上
MDs[i]=MD[j];
__syncthreads();//直到所有的线程都执行到这条语句,才继续执行该语句的吓一跳指令
func(MDs[i],MDs[i+1]);
if(someFunc())
{
__syncthreads();
}
else
{
__syncthreads();
}
上下文存储池中的内存被分隔为越多的小上下文(执行任务),越有利延迟掩藏。什么是延迟掩藏?就是让一个计算单元不能闲着,在一个计算单元中事先放置很多的任务,一件任务不能做,就给换另一件任务做,直至最后计算单元一刻不停的将所有任务都做完。
延迟隐藏就好比公司里的老板,老板总不希望一个员工有任何闲着的时候, 如果老板让员工去去取快递,员工说快递还没到;这种情况下,老板会说那你先去把技术报告整理一下发我;如果员工说,电脑还没开机,那老板可能说,你先擦擦桌子;等等等等,总之,不停的给你任务,不会让你闲着,这就是延迟隐藏,通过不停的切换任务,让计算单元一直在计算状态中。
如上图,第一个任务不能做的时候,去做第二个任务,第二个任务也不能做的时候,那就去做第三个任务,第三个任务还不能做,那就去干第四个任务,第四个任务还不能做,也许第一个任务就能做了,依次类推,达到block不会闲着无事可做的状态。
1.根据硬件的参数,应该要计算出CUDA中线程的组织情况。如GT200的GPU:
2.线程调度的基本单位是warp,warp是块内的一组线程,每个warp由块内32个连续的线程组成,warp运行与同一个SM上,且每个warp内的线程执行同样的指令。如果warp内部线程沿不同分支执行,会导致线程串行执行,如下图所示案例,只有x>0的情况处理完成后,处理x<0的线程才会执行。
3.每个warp含32个线程,但如果每个SM只有8个SPs,如何分配SP来执行各个线程相应的指令?
4.来几道计算题
局部寄存器实质上是存储于global memory,因此,读写速度慢,作用域是每个线程,通常用于存储自动变量数组。
全局存储器可被所有block读写,并且可被Host读写,内存容量大,但是其读写速度慢,延时长(大概100周期左右)
常量存储器,短延时,高带宽,当所有线程访问同一位置时只读;存储于global memory但是有缓存;Host主机端可读写;内存容量小,大多64KB
CUDA程序优化需要兼顾两个方面才能达到最优性能。一个方面是有效的数据并行算法,另一个是针对GPU框架特性的优化。
在一次global memory 读操作和实际用到这个数据的语句中间,插入独立于以上数据的指令,可以隐藏访存延迟。示例如下:
float m = Md[i];//数据预读
float f = a * b * c;//中间执行独立语句,掩藏m读取的时间
float f2 = m * f;//在此之前已经预读m
CUDA中有些指令的疏忽也会造成程序的性能下降,如为float常量添加f(10.0f)可以避免float自动像double类型转换;CUDA还提供两种运行时数学库函数,func()精度高,速度慢,__func()精度低,速度快,如sin(x)的精度高,计算速度慢, __sin(x)的精度低,但计算速度快。
就是global Memory规则访存,一个线程访问对应索引数据,相邻线程访问相邻数据,这样访问全局内存,才会获得最优的访存性能。合并访存如下图所示:
如果每个线程访问数据出现偏移或者步长,则会造成带宽性能下降,下图是偏移为1的线程访存示例
由下图可以看出,偏移会导致带宽性能有所下降
下图是以步长为2跳动访问内存示例
由下图可以看出,以一定步长跳动访问数据,会是带宽性能急剧下降
如果遵循一些简单的原则,GPU硬件在数据可并行计算问题上,可以达到很好的性能:
1.有效利用并行性
2.尽可能合并内存访问
3.合理高效的利用shared memory
4.减少bank冲突
并行化的可扩展性有极限,及达到极限后,无论你的物理核数再增加多少,并行效率不再提高
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。