最近有在复习深入理解计算机系统,毕竟马上就要考试了还啥也不会,正好以前做实验的时候看过一个学长写的blog,然后他有提到网络旁注的事情,讲到的是blocking技术,然后我感兴趣就去看了一眼……
b话不多说,先来看看题目
题目
简单来说就是计算矩阵乘法,问题是如何利用cache来比较高效的乘。
那来看看他给的码,最终目的是把结果写到C里
代码
1 void bijk(array A, array B, array C, int n, int bsize)
2 {
3 int i, j, k, kk, jj;
4 double sum;
5 int en = bsize * (n/bsize); /* Amount that fits evenly into blocks */
6
7 for (i = 0; i < n; i++)
8 for (j = 0; j < n; j++)
9 C[i][j] = 0.0;
10
11 for (kk = 0; kk < en; kk += bsize{
12 for (jj = 0; jj < en; jj += bsize) {
13 for (i = 0; i < n; i++) {
14 for (j = jj; j < jj + bsize; j++) {
15 sum = C[i][j];
16 for (k = kk; k < kk + bsize; k++) {
17 sum += A[i][k]*B[k][j];
18 }
19 C[i][j] = sum;
20 }
21 }
22 }
23 }
24 }
这尼玛绝对是天书吧!!!
反正我第一眼是啥都看不懂【卑微】
理解
然后我们来看一下他给的一张图,似乎能理解一些了呢(?)
那现在来简单的解释一下
首先解释一下bsize,bsize应该是假设当中的一个cache里面最多能同时存bsize x bsize个元素,而我们的题目就是以bsize作为单位进行操作的。
1-9行就没什么好看的了,主要就是向下去一个bsize的整数倍,方便操作,然后初始化一下。
从第11行开始,首先是
for (kk = 0; kk < en; kk += bsize{
从他给的图里面我们知道kk就是A的纵坐标,B的横坐标
然后是
for (jj = 0; jj < en; jj += bsize) {
再看图,kk是B的横坐标C的纵坐标
啊猜一猜,大概是在B里面取了一个bsize x bsize的block,然后做完运算之后以bsize为单位横着写一条,然后依次竖着写进了C
那我们接着往下看
for (i = 0; i < n; i++) {
看第一张图我们能知道i是A的横坐标
注意到他给出的边界条件是n,他的意思是先横着取size大小的一行数,然后我们知道cache会同时取出一行缓存起来,这样就不用每次都到主存里去取了。这样一直取到最后一行,然后再换到第一行继续从第bsize个数开始以bsize为单位再取一条然后从上到下竖着这样一行一行地取
这里就初步利用了空间局部性
我自己画了张图,假设n = 8,bsize = 4,他的意思是在A里面以横着的4个元素为单位竖着取
14 for (j = jj; j < jj + bsize; j++) {
15 sum = C[i][j];
16 for (k = kk; k < kk + bsize; k++) {
17 sum += A[i][k]*B[k][j];
这里就是他开始缓存了B中的bsize x bsize大小的block,然后竖着一列一列取bsize大小的一条,跟之前我们取来的A中横着的一条做运算,然后加到sum去,因为这里只做了1/4步运算
这里B也有良好的空间局部性
因为B这里的一整个块都被缓存了,每次都会把这一整个块的元素全部利用完然后再丢弃他,以后就再也不碰了呢,充分利用了cache
C也有良好的空间局部性,因为每次都是一条一条接着写的
总结
这个代码自己写的话我个人认为应该从里往外写
一直在动的是A里面在(k-1)bsize->k(bsize)这个区域里面一直在动的那个纵坐标,B里面是控制它在一小列里面切换的横坐标,所以最内层循环应该是k
接着A里面一小行遍历完了,B里面一小列遍历完了就改切换B当中的一小列,所以再外面应该套j
那一个block都与A这个小行做完了运算,该换行了咯,再外面就应该套i
这里A里面的bsize为单位的横坐标,B里面的bsize单位的纵坐标都是最后再动的,因为要保证一块全部都缓存完扔掉以后就不再用到了,所以他们应该在最外层循环
个人认为最后可能还有一些剩余,因为前面向下舍入了嘛。所以应该还需要加上类似于第五章的循环展开这样把剩余的元素给做完运算。不过不重要了hhh