Thursday, September 17, 2009

图解CUDA中用多线调度执行来隐藏内存访问延迟的机制

近在捣鼓CUDA,实现的几个小算法速度能提高到40~50x单线CPU的程度。可惜现在这边弄这个的人不多,基本没有“真人交流”。希望今后能用来做一些实际的项目,发挥一下潜能。嘿嘿。

最近应该会写一些心得,记录一下学习过程。我其实是一直有心做这样的事情,只是懒…… 从今天开始,哈哈。

图解CUDA中用多线调度执行来隐藏内存访问延迟的机制

GPU
和device RAM之间带宽虽宽(如GTX285的150GB/s),不过内存访问延迟依旧很大(400~600 cycles)。CPU用cache可以很好的解决内存延迟;不过GPU由于要处理的通常是大规模的数据集,源数据的locality或许并不高,即便加上很大的data cache效果也不一定好,所以索性放弃了data cache,换而采用用户可控的shared memory来开发data locality。最重要的,虽然GPU的一个处理器(SM - Streaming Multiprocessor)同时只能执行一个warp的线程(32个),但却可以容纳大量的活动线程(active threads <= 1024)。当一个warp由于访问内存而被阻塞时,SM可以马上转为执行其他ready的warp,直到其被内存访问阻塞。只要这样转一圈的时间(执行完一圈其他的warps再转回来到第一个被阻塞的warp的时间)大于600 cycle的延迟,这个延迟就被隐藏了。

下图示意了内存访问延迟不能被完全隐藏的情况。假设SM上只有4个warp,他们需要从内存里面读取数据,在上面计算一把,循环这个过程。当然,我们也可以看成每个warp计算一次就终止了,之后新的warp会产生出来,代替原来那个warp的位置。从图里面很容易看出来虚线部分的latency是怎么被隐藏的;也很容易看出来因为每个warp的计算不够长,所以没有能够隐藏掉所有的latency。


放到现有的G200的architecture上来,每个SM最多1024个active thread,也就是32个warp。每个warp执行一个基本指令需要4个cycle。也就是说,要隐藏600 cycles需要每个warp平均执行 600/4/32 = 4.69个基本指令。这就是为什么推荐compute to memory ratio至少大于5的原因。这还是建立在理想的1024个active thread的情况下的。如果SM上thread少,这个比例还要提高。