在上一节,我们只是谈到了CUDA的用法,但是实际上很多高级的优化的技巧并没有涉及,所以本讲针对带宽、指令、调度的最大化吞吐量三个方面进行优化。通过举一个例子让这些优化过程更加清晰。
最优存储设计
SOA vs AOS
哪一种更好呢?其实要看具体情况,比如:
以上的话使用SOA更好。
这种情况下,使用AOS更好。
存储对齐
在对DRAM的数据进行存取的时候,一般是取整个segment的数据。如果把很多次的取内存放在一次取数据的时候,就可以产生比较高的带宽。
使用共享存储
- 把数据从DRAM搬运到共享存储
- 进行同步
- 在共享存储上对数据进行存取
- 进行同步
- 把数据写回DRAM
使用double buffer可以使得效率更高:
使用共享存储有两种方式:一种是在编译时固定共享存储的大小,另外一种就是在运行的时候使用。
存储bank 冲突
当多个线程对同一个bank的不同32 bit的字进行访问的时候,会发生冲突。
比如在下面这个例子里,左边的和最右边的都没有冲突,冲突发生在中间的。中间的存在两个线程访问统一个bank。
那么怎么消除冲突呢?
最左边的采用的是随机排列的方法,中间的是多个线程指向同一个bank的word,最右边的是所有的线程指向同一个bank的word。
补全
使用补全的方式使得存储对齐,方便操作。比如是63字节对齐,那么就给存储的区域补全到64字节对齐。
最大化指令吞吐量
branch divergence
每次指令执行时候,SIMT就会选择一个warp进行执行,每个warp的最高效率就是32个线程一起执行。
一般来说,指令越少的话,每个线程就能更快执行。因为需要等待warp里的线程执行结束以后,才能回到原来的轨迹继续执行下去。所以每个线程执行得越快,就能越快回到正常的执行轨迹上。但是有的条件判断使得轨迹边长,为了提高吞吐量,需要做一些优化。比如下面的程序:
optimizing to mix
做循环展开可以提高IPC,具体使用unroll的方法如下:
最大化调度吞吐量
就是在使用资源方面,看资源使用率。
使用intrinsic 函数提高performance。
优化算法
数据并行算法
Map
映射就是给了一个list,然后通过一个函数得到一个新的list并返回。
映射这部分和别的部分不相关,无交集。并不会受到别的函数的影响。
Reduce
就是把线性执行的比如加法,减法变成通过集合律变成二分类型。
reduce会存在问题,他使得线程的利用率下降而且不均匀分布。比如随着线程数量减少,只需要一个block可以搞定,但是现在它占据了好几个block,占据了资源,影响了速度。
把上面的东西改成:
通过哈希映射把多个线程到了同一个线程。
以上步骤还有同步线程的操作,非常耗时间,所以改成:
Scan
就是对list进行累计操作,比如累加。
那么怎么减少访问次数呢?
数据并行算法的步骤
compact(压缩)
对原数组处理产生更小的数组。
find unique(找到独特的项)
去除冗余,找到独特的。
建立flag array
就是在去除冗余的时候,建立哈希表去除冗余。比如一个字符串中是否出现某个字符。
直接只用一个数组记录,0为没有,1为有。这是非常简单的编程思路。