目录
任务描述
32 × 32
baseline
try1:给对角线上的块再次细分块
try2:对角块依次错位
成功:对角块两两交换
成功:其他方法
64 × 64
baseline
对角线优化+普通块转置顺序调整(7.6/8分)
61 × 67
成功:bsize=16*16
测试
要求在trans.c中编写一个转置函数,从而导致尽可能少的miss。缓存的参数是 (s = 5, E = 1, b = 5)。三种测试用例的矩阵大小分别为:
• 32 × 32 (M = 32, N = 32)
• 64 × 64 (M = 64, N = 64)
• 61 × 67 (M = 61, N = 67)
规定:仅使用12个局部变量,不能使用递归,不能修改A数组(可任意修改B数组),不允许使用malloc。
提示:你的代码只需要针对这三种情况是正确的,并且你可以针对这三种情况进行专门优化。特别是,你的函数完全可以显式检查输入大小并实现针对每种情况优化的单独代码。
下图是32*32数组的分块情况,每一个小格代表一个block(由前提条件b=5,得出一个块是32字节,由于是int数组,所以一个block能存8个元素)
上面是16个大块。
转置时依次扫描大块1~16,将块内元素转置后放在数组B对应的位置。可以发现,除了对角块,其余都是没有冲突未命中的。
int i, j, k, q, tmp;
if (M == 32 && N == 32)
{
for (k = 0; k < 4; k++)
{
for (q = 0; q < 4; q++)
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + j][k * 8 + i] = tmp;
}
}
}
}
}
由于PartA我们已经实现了模拟器了,所以miss的值肯定是能手算出来的。我们先大概算一下。
对于图上未着色的12个大块,12*16=192;对于对角线上的4个大块(着色的),4*(10+4+4+4+4+4+4+3)=144;故大约是192+144=336。
测试程序算的是344,大差不差。但仍需优化。 修改思路就是对于对角线进行进一步优化。
本来一个大块是8*8的,对于对角线,再一分为四,变为4*4。
还是先来预算一下结果:12个大块跟baseline一样是192;对角线上8个4*4块:8*(6+4+4+3)=138;其余8个4*4块:8*8=64;总计192+138+64=392。
测试程序计的数也是392,这种方法更差了。
int i, j, k, q, i2, j2, k2, q2, tmp;
if (M == 32 && N == 32)
{
for (k = 0; k < 4; k++)
{
for (q = 0; q < 4; q++)
{
if (k == q)
{
for (k2 = 0; k2 < 2; k2++)
{
for (q2 = 0; q2 < 2; q2++)
{
for (i2 = 0; i2 < 4; i2++)
{
for (j2 = 0; j2 < 4; j2++)
{
tmp = A[k * 8 + k2 * 4 + i2][q * 8 + q2 * 4 + j2];
B[q * 8 + q2 * 4 + j2][k * 8 + k2 * 4 + i2] = tmp;
}
}
}
}
}
else
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + j][k * 8 + i] = tmp;
}
}
}
}
}
}
具体来说,转置完的1号大块存在6号大块,6号存在11号,11号存在16号。16号进行普通转置。
int i, j, k, q, tmp;
if (M == 32 && N == 32)
{
for (k = 0; k < 4; k++)
{
for (q = 0; q < 4; q++)
{
if (k == q && k != 3)
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + 8 + j][k * 8 + 8 + i] = tmp;
}
}
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = B[k * 8 + i + 8][q * 8 + j + 8];
B[k * 8 + i][q * 8 + j] = tmp;
}
}
}
else
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + j][k * 8 + i] = tmp;
}
}
}
}
}
}
现象:变好,m=305(只差一点点)
具体来说,A的1号转置完成后存到B的6号,A的6号转置完存到B的1号去。然后,B的1号和6号交换。11号和16号同理。
现象:m=276,成功。
int i, j, k, q, tmp;
if (M == 32 && N == 32)
{
for (k = 0; k < 4; k++)
{
for (q = 0; q < 4; q++)
{
if (k == q && k % 2 == 0)
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + 8 + j][k * 8 + 8 + i] = tmp;
}
}
}
else if (k == q && k % 2 == 1)
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 - 8 + j][k * 8 - 8 + i] = tmp;
}
}
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = B[k * 8 + i][q * 8 + j];
B[k * 8 + i][q * 8 + j] = B[k * 8 - 8 + i][q * 8 - 8 + j];
B[k * 8 - 8 + i][q * 8 - 8 + j] = tmp;
}
}
}
else
{
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
tmp = A[k * 8 + i][q * 8 + j];
B[q * 8 + j][k * 8 + i] = tmp;
}
}
}
}
}
}
以上思路是我独立想出的。看了其他人的解法,在此补充一下:
对角线处发生的较多conflict miss的原因是,A和B相同大块之间是占有相同索引位置的cache的。如果先把A的对角线上的大块原封不动的挪到对应位置的B大块,然后让B大块原地转置,原地转置是不存在conflict miss的。
问题就变成了怎么实现“把A的对角线上的大块原封不动的挪到对应位置的B大块”,因为同时访问AB的对应位置就会发生conflict miss。
其实示例程序的tmp变量是个提示。可以多来几个局部变量,当做一个临时数组。也就是将循环最内层完全展开。
这种方法的代码请见 https://zhuanlan.zhihu.com/p/410662053
现象:m=1892(满分1300)
对角线转置方法:
普通块:
if (M == 64 && N == 64)
{
//k q 将64*64棋盘分为64个8*8的小格
for (k = 0; k < 8; k++)
{
for (q = 0; q < 8; q++)
{
//处理对角线
if (k == q)
{
int bias = 8;
if (k % 2 == 1) bias = -8;
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
B[k * 8 + bias + j][q * 8 + bias + i] = A[k * 8 + i][q * 8 + j];
for (i = 0; i < 4; i++)
for (j = 4; j < 8; j++)
B[k * 8 + bias + j][q * 8 + bias + i] = A[k * 8 + i][q * 8 + j];
for (i = 4; i < 8; i++)
for (j = 4; j < 8; j++)
B[q * 8 + bias + j][q * 8 + bias + i] = A[k * 8 + i][q * 8 + j];
for (i = 4; i < 8; i++)
for (j = 0; j < 4; j++)
B[k * 8 + bias + j][q * 8 + bias + i] = A[k * 8 + i][q * 8 + j];
}
else
{
// 对于不在对角线上的12个8*8的普通格,每个格还是一分为四,每小格为4*4,按照1,2,4,3的顺序从A转置存到B
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
B[q * 8 + j][k * 8 + i] = A[k * 8 + i][q * 8 + j];
for (i = 0; i < 4; i++)
for (j = 4; j < 8; j++)
B[q * 8 + j][k * 8 + i] = A[k * 8 + i][q * 8 + j];
for (i = 4; i < 8; i++)
for (j = 4; j < 8; j++)
B[q * 8 + j][k * 8 + i] = A[k * 8 + i][q * 8 + j];
for (i = 4; i < 8; i++)
for (j = 0; j < 4; j++)
B[q * 8 + j][k * 8 + i] = A[k * 8 + i][q * 8 + j];
}
// 交换
if (k == q && k % 2 == 1)
{
for (i = 0; i < 4; i++)
for (j = 0; j < 4; j++)
swap(&B[k * 8 + i][q * 8 + j], &B[k * 8 - 8 + i][q * 8 - 8 + j]);
for (i = 0; i < 4; i++)
for (j = 4; j < 8; j++)
swap(&B[k * 8 + i][q * 8 + j], &B[k * 8 - 8 + i][q * 8 - 8 + j]);
for (i = 4; i < 8; i++)
for (j = 0; j < 4; j++)
swap(&B[k * 8 + i][q * 8 + j], &B[k * 8 - 8 + i][q * 8 - 8 + j]);
for (i = 4; i < 8; i++)
for (j = 4; j < 8; j++)
swap(&B[k * 8 + i][q * 8 + j], &B[k * 8 - 8 + i][q * 8 - 8 + j]);
}
}
}
}
要求m<2000,比较宽松。
if (M == 61 && N == 67)
{
int bsize_x = 16;
int bsize_y = 16;
for (k = 0; k <= 67/bsize_x; k++)
for (q = 0; q <= 61/bsize_y; q++)
for (i = 0; i < bsize_x && k*bsize_x+i<67; i++)
for (j = 0; j < bsize_y && q*bsize_y+j < 61; j++)
B[q * bsize_y + j][k * bsize_x + i] = A[k * bsize_x + i][q * bsize_y + j];
}
注意测试程序driver.py要用python2运行,python3是报错的