1. 文档简述
Part B 是在trans.c
中编写矩阵转置的函数,在一个 s = 5, E = 1, b = 5 的缓存中进行读写,使得 miss 的次数最少。
要求最多只能声明12个本地变量。
根据课本以及 PPT 的提示,这里肯定要使用矩阵分块进行优化。
2. 代码以及思路
接下来想想优化,注意,我们这里需要优化的不是程序运行的时间,而是cache的命中率,也就是说像常见的把一些代码从循环中移出之类的优化在这里没有意义。
32×32:分析一下:给出的信息是(s=5, E=1, b=5),s=5,也就是说缓存有32个组,E=1,即每组一块,b=5,也就是说每一块数据位存储了32个字节,也就是8个int
对于A来说,A是顺序访问的,每一块8个int会有1个导致miss,剩下7个hit,不用对A的访问进行操作
主要是B。首先我们要清楚,这里缓存用的是一路组相联,组数为32,也就是说组索引最大为32,到33的话就会覆盖第一个组的块。B是存储在内存中的,这里是32×32的int矩阵大小,按照一路组相联,在内存中每一行32个int数,也就是对应了4个缓存块(32/8),由于是一路组相联,也就是对应了4个组。那么在内存B中每8行就会耗空所有的组索引,如果直接竖着写,写到内存B第9行时,组索引用完了,他就会去覆盖第1组的块,这才是组相联的映射规律,当然就会导致miss
那么A在读完一个缓存块的8个int后,会导致B不命中8个缓存块,B会修改这8个缓存块的每一个的第一个int(4字节),如果我们不优化,那这8个缓存块的剩下7个int就没用到,由于组索引耗空,到第9行就覆盖了第一组里面的块,那就gg
因此我们展开循环,A每读一个块的8个int后,我们竖着读下一个块,一共竖着读8个块,这样就能让B修改那8个缓存块的后7个int,并且正好能够完全利用契合组索引(其实在这里是巧合,不一定你的组索引数一定能契合你的数据块数,比如下面的64×64)
miss达到了287
打个更形象的比方:想象你有一个书架,书架上有32个盒子,每个盒子能放一个大章鱼玩具。这些玩具是存储在一个巨大的盒子里,里面有很多小格子,每个格子可以放一个玩具。
现在,当你要玩具时,你会从书架上的盒子里挑一个玩具出来。但是注意,如果你挑到了第33个玩具,它会覆盖掉第一个盒子里的玩具。
接着,假设你拿出来了一个玩具,这个动作会导致盒子里的8个小格子(表示一个玩具的8个部分)都需要被重新装饰。如果你不合理安排,可能会导致某些格子没有被用到,而且当你再拿第9个玩具的时候,可能会覆盖掉前面的装饰。
要解决这个问题,你可以一次性拿出8个玩具,这样就能保证每个格子都被重新装饰,而且不会有装饰被覆盖的问题。这样做能够更高效地利用书架上的空间。
void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
int i, j, k;
int tmp_0, tmp_1, tmp_2, tmp_3, tmp_4, tmp_5, tmp_6, tmp_7;
for (i = 0; i < N; i+=8) {
for (j = 0; j < M; j+=8) {
for (k = i; k < i+8; k++) {
tmp_0 = A[k][j];
tmp_1 = A[k][j+1];
tmp_2 = A[k][j+2];
tmp_3 = A[k][j+3];
tmp_4 = A[k][j+4];
tmp_5 = A[k][j+5];
tmp_6 = A[k][j+6];
tmp_7 = A[k][j+7];
B[j][k] = tmp_0;
B[j+1][k] = tmp_1;
B[j+2][k] = tmp_2;
B[j+3][k] = tmp_3;
B[j+4][k] = tmp_4;
B[j+5][k] = tmp_5;
B[j+6][k] = tmp_6;
B[j+7][k] = tmp_7;
}
}
}
}
64×64:根据上面32×32我们得知,当内存每行变为64个int时,也就是每行对应了8个缓存块,那内存B中的4行就会耗尽组索引,所以靠8×8展开是不行的,因为第5行就会导致组索引耗空不命中
我们先改为4×4展开,来满足组索引,这时miss来到了1699,接近满昏了
那继续优化的点在哪里,就是cache块没有完全利用,我们的cache有8个int啊,4×4展开肯定不能完全利用,那我们还是可以用8×8展开,只不过8×8中再4×4,具体是每一个8×8块中,分别移动左上,右上,左下,右下的4×4块,说白了就是套两层,8×8整体移动,只不过8×8中不是直接用,而是分四次4×4的搬运即可,miss来到了1179
void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
int i, j, k;
int tmp_1, tmp_2, tmp_3, tmp_4, tmp_5, tmp_6, tmp_7, tmp_8;
for (i = 0; i < N; i += 8) {
for (j = 0; j < M; j += 8) {
for (k = 0; k < 4; i++, k++) {
tmp_1 = A[i][j];
tmp_2 = A[i][j+1];
tmp_3 = A[i][j+2];
tmp_4 = A[i][j+3];
tmp_5 = A[i][j+4];
tmp_6 = A[i][j+5];
tmp_7 = A[i][j+6];
tmp_8 = A[i][j+7];
B[j][i] = tmp_1;
B[j+1][i] = tmp_2;
B[j+2][i] = tmp_3;
B[j+3][i] = tmp_4;
B[j][i+4] = tmp_5;
B[j+1][i+4] = tmp_6;
B[j+2][i+4] = tmp_7;
B[j+3][i+4] = tmp_8;
}
i -= 4;
for (k = 0; k < 4; j++, k++) {
tmp_1 = A[i+4][j];
tmp_2 = A[i+5][j];
tmp_3 = A[i+6][j];
tmp_4 = A[i+7][j];
tmp_5 = B[j][i+4];
tmp_6 = B[j][i+5];
tmp_7 = B[j][i+6];
tmp_8 = B[j][i+7];
B[j][i+4] = tmp_1;
B[j][i+5] = tmp_2;
B[j][i+6] = tmp_3;
B[j][i+7] = tmp_4;
B[j+4][i] = tmp_5;
B[j+4][i+1] = tmp_6;
B[j+4][i+2] = tmp_7;
B[j+4][i+3] = tmp_8;
}
j -= 4;
j += 4;
for (i += 4, k = 0; k < 4; i++, k++) {
tmp_1 = A[i][j];
tmp_2 = A[i][j+1];
tmp_3 = A[i][j+2];
tmp_4 = A[i][j+3];
B[j][i] = tmp_1;
B[j+1][i] = tmp_2;
B[j+2][i] = tmp_3;
B[j+3][i] = tmp_4;
}
i -= 8;
j -= 4;
}
}
}
61×67:这个要求很松,只需要2000即可,分块展开即可,直接 16 × 16 的分块就能通过