一 在存储器方面的优化
1.主机的内存分配
为了防止程序中分配的内存为分页内存,有被置换出内存条的可能,可以调用cudaAllocHost函数来创建主机内存为非分页内存。这样在从主机至设备,设备至主机的内存复制操作时,可以大幅减小操作耗时,但是有个劣势是非分页内存始终占据内存条空间。
2.gpu上,线程函数对全局存储器的访问优化(合并访问)
由于gpu上全局内存为对齐好的(如,调用cudaMalloc分配的存储器,能够保证其首地址至少会按256byte进行对齐)。gpu高性能运算之CUDA 这本书上说:存储操作的吞吐量是每个时钟周期8个操作。当访问local/global memory的时候会有额外的400~600个时钟周期的访问延迟。
一个half-warp内的线程访问的地址如果是连续的,则它们对全局存储器的访问可以进行合并访问(假如访问的首地址是64byte的整数倍,即段对齐,合并访问一次就够)。若一个half-warp内线程访问的地址是间隔很大,那么它们对全局存储器的访问将会做很多次,从而大幅增加程序执行时间。
3。gpu上,线程函数对共享存储器的访问优化(bank conflict)
gpu上,shared memory 的访问延迟只有local/global memory的百分之一,访问速度很快(因为它位于gpu片内)。
shared memory 被划分为大小相等,能被同时访问的存储器模块,并称其为bank。由于不同存储器(bank)可以互不干扰的同时工作,因此对位于n个bank的n个地址的访问能同时进行,此时有效带宽是同时对单个bank的n个数据访问的n倍。
bank的组织方式为:每个bank的宽度为32bit,相邻的32bit字被组织在相邻的bank中,每个bank在每个时钟周期可以提供32bit的带宽。
一个half-warp的16个线程对shared memory 访问并不是在不同的bank中时,就会发生bank conflict。
4.常数存储器的使用。
常数存储器的数据位于显存,但其拥有缓存加速。每个sm有8kb的常数存储器缓存(不知道对不同的卡,此值是否一致)。
若有大尺寸的查找表等常数数组,可以考虑放入常数存储器中,以获得缓存的访问加速。在相机标定中可以用用看。
5。纹理存储器的使用
纹理存储器能够通过缓存利用数据,只要用来存放图像和查找表。它在硬件上实现线性插值,很适合做图像几何变换。
二,指令流的优化
1.指令流并行
通常会讲到让主机对gpu的内存拷贝、内核函数执行做异步处理
但在gpu内线程里面的指令也可以通过指令并行,获得好的加速效果。gpu高性能运算之CUDA 书里的matrixMul2例子(难理解,但是很妙),其实也用了指令并行,虽然它没有指名说。
如果上下两条指令没有逻辑关系,那么不等确认第一条执行完毕,第二条就会被执行,就像并行一样。也就是说,可以通过指令并行,实现隐蔽掉访存等操作的延时。
对于前后无关的循环,可以通过#pragma unroll 指令,使for循环展开执行,从而使其指令并行。
2. 算数运算的耗时。
gpu单元对 除法和取余等操作的执行时比较慢的,除法操作是每个周期小于1个操作,对三角函数 sinf(x)、cosf(x)、exp(x),也是一个周期1个操作。
gpu对单精度浮点数的运算一般比整数算术运算好,如:单精度浮点数的乘法和乘加操作时是 每周期8个操作,而对32bit整数乘是每周期2操作。对整数除和模运算的开销特别大,应该尽量避免,或是用位运算代替。
我在编程中发现: (gpu内核函数的片段)
float tu = u*cosf(theta) - v*sinf(theta) + 0.5f;
float tv = v*cosf(theta) + u*sinf(theta) + 0.5f;
if(x<width&&y<height)
outputData[y*width + x] = inputData[ y*width+x];
如果不加两条红色指令,执行时长为0.35ms,加上后就成4ms了。这里也是有个疑问,按说访存指令存在延迟在几百个周期,远比算数指令慢,但加了三角函数运算,竟然差距这般大。