4.4 核函数可达到的带宽
4.4.1 内存带宽
理论带宽是指能达到的绝对最大带宽,有效带宽是核函数实际达到的带宽,计算方法是:
有效带宽(GB/s) = (读字节数+写字节数)× 10^-9 / 执行时间
4.4.2 矩阵转置问题
对于二维矩阵存在一维数组时,转置代码如下:
for(int iy=0;iy<ny;i++)
for(int ix=0;ix<nx;i++)
out[ix*ny+iy]=in[iy*nx+ix];
其中nx和ny时列数和行数。
显然对于读取的时候,是合并访问的,而写入时交叉访问的。
本节剩余部分讲述两种方案,行读列写和列读行写。(上面的代码显然是行读列写的)。
如果禁用一级缓存二者性能一样,如果启用的话列读行写会好,对于读来说一级缓存中可能会保留下几次的数据,而非在全局内存上读取,对于写来说都一样,因为没有一级缓存缓存写操作。
我的理解是关键是线程束是基本操作单元,比如16*16的块来说:
- 如果是行读的话,一个线程束会先读取行的前32个(第一行和第二行),这种读取是直接全部访问全局内存的,因为这是一个基本操作,所以算是第一次读取,不会去问缓存,在读之后的32个时(第三行和第四行),前面的缓存是没有给到这块的,还需要接着读全局内存。(缓存在第二块的第一行和第二行中)
- 如果是列读的话,一个线程束读取列的前32个(第一列和第二列),这种情况他的缓存是第一块和第二块全部的数据,之后再读都会命中,相比于读8次全局内存的行读取来说,虽然他快,但是八次的话也会比这种只读了一次后面全都命中缓存的情况慢。
4.4.2.1 为转置核函数设置性能的上限和下限
创建两个拷贝矩阵的核函数来考虑上下限,第一个是行读行写,这个是上限,全是合并访问。第二个是列读列写,是下限,全是交叉访问。
文中给出了其结果,上限是理论带宽的70%,下限是30%。
4.4.2.2 朴素转置:读取行与读取列
就是跟之前的代码类似,换成核函数里即可,是最朴素的方法。
发现结果是列读行写快,原因与前面提到的一样。禁用一级缓存后列读行写就变慢了不少。
4.4.2.3 展开转置:读取行与读取列
所谓展开就是一个线程合并处理原来的多个块内同位置的线程:比如展开因子为4,那么对应的就是第一个线程会处理第一块到第四块中的所有的第一个线程。(当然别忘记对应调整网格大小)
这时候启用一级缓存会发现列读行写会比朴素的快了一点。
4.4.2.4 对角转置:读取行与读取列
SM中的块由于使用时间不同,旧的块用完会被换成新的,时间久了他们就不太连续了。
(谭升博客中讲得理解博主觉得很对,此处的对角转置只不过是一种人为定义的打乱方式,而非硬件决定的,目的是为了使读取更加随机一点,防止全在一个分区内。)
本节新提出了一个坐标方法叫对角坐标系,可以与直角坐标转换
4.4.2.5 使用瘦块增加并行性
就是不用方方正正的块如16*16的,同样的并行程度改为8*32,就会好很多。
4.5 使用统一内存的矩阵加法
统一内存单纯是为了增加可读性和易维护性的,就是一种对复杂分配方式的一种封装,所以最好还是手动分配比较好。