技术开发 频道

利用CUDA并行化的一点小感受

  【IT168 技术】

  注:本文为IT168&NVIDIA联合举办的“如何并行化我的应用”方案征集活动参赛作品。本次方案征集活动详情见:http://cuda.itpub.net/thread-1299715-1-1.html。近期活动的大部分方案,将会逐步与大家分享,不可错过哦!

  CUDA ZONE专区:http://cuda.it168.com/

  CUDA技术论坛:http://cuda.itpub.net

  我简单的谈一下我使用CUDA进行并行化计算的一点感受。我不是专业的计算机编程人员,我们实验室也不是计算机相关的实验室,计算只是辅助实验的一个部分。因此,我们对计算速度的需求并不是特别敏感,更多的考虑代码书写和调试的简便性,一般的算法都是拿Matlab写了,能运行能算出结果就行。所以对于GPU编程的应用,是一次很偶然的尝试,但同时让我们充分的感受到了GPU并行计算的强大魅力。

  我们的项目跟三维成像相关,属于Computer Tomography的一种。通过采集样品360度下的投影图像,经过计算重构出样品每一层的断层图,最终得到包含样品内部信息的三维图像。整个计算的开销很大,在较低的分辨率下,使用3.66GHz的core2 处理器,完成一次重构需要3个小时的时间。随着分辨率增加一倍,计算时间将上升到24小时。一方面由于采用的matlab编程,matlab本身是解释型的,在面对大量循环时效率较低;另一方面处理器本身的计算能力也非常有限。我们做一次实验只要一小时不到,而计算得到实验的结果却要数个小时,计算和实验时间的不匹配使得我们的实验结果不能被及时反馈,降低了我们的工作效率,考虑到GPU强大的计算能力,我们就有了通过CUDA进行GPU编程,加速计算过程的念头。

  我们发现重构的计算过程中,断层图中每一个像素的计算都是相对独立的,主要包含两步求和的操作。于是,每一个像素的计算由一个线程实现。在第一步的求和操作中,我们发现矩阵中同一行的原始数据被同一行的像素计算所复用,于是我们把每一行作为一个线程块,定义了一维的线程结构和一维的线程块结构。计算同一行的像素的线程处在同一个线程块中,它们预先把所有计算需要的数据读入到共享存储器中,减少了对全局存储器的访问,提高了效率。16KB的共享存储器最多可以放入4096的元素,我们在最高分辨率的情况下也只使用1024个元素,完全满足我们的需要,并有充足的发展空间。这一步操作,在GTX260+上实现了42倍的性能提升。

  而第二步的求和就比较复杂,每次都需要计算出数组的下标,然后再到全局存储器中去查找所需要的数据,因此显存带宽就成了严重的瓶颈。要减少对全局存储器的访问,所有可能被访问的原始数据都要被缓冲,这就需要有一个1024*4*400 byte(400KB)的片上存储器,能够被整个grid内的线程共享,在我们使用的GT200b核心上,这个是不可能做到的。于是我们只能对1024×1024的目标矩阵按照16×16的二维block进行分割,线程块内部也采用二维的方式安排线程。每次的计算不可避免的访问全局存储器。尽管如此,还是在GTX260+上获得了136倍的性能提升。对显存的频繁访问成为了这一步应用的瓶颈,我们尝试使用了CUDA的纹理缓存,将原始数据矩阵按照二维纹理的形式绑定到纹理存储器,进一步获得了约10%的性能提升。我们还是比较期待新一代的GTX480,费米芯片带有可被所有流处理器共享的768KB二级缓存,足够缓冲我们的400KB原始数据,相信能明显减少全局存储器的访问压力,进一步提升性能。

  最后,我们在2块GTX295的4个GPU上,实现了将原本需要20小时的计算,减少到26s以内,使我们的实验结果可以实时的被观测到,大大提升了我们的工作效率。并且,出于对Matlab文件IO、设备控制和图像预处理的需要,我们的主程序保留了matlab代码,CUDA计算部分用C语言编写,通过mex接口与主程序结合。nVIDIA官方提供了Matlab CUDA程序接口的使用说明,对于我们来说十分方便。总体来说,我们对于这次CUDA的应用十分满意,CUDA使我们以较低的成本获得了巨大的性能提升,并且代码可以很容易的移植到新一代GPU上,有着充分的提升空间。

0
相关文章