离该实验结束也已有好几个星期,忘却的救主快要降临了罢,我正有写一点东西的必要了。
哇,再不写就写不出来了QWQ...
(Ⅰ)任务:
编写一个cache模拟器,该模拟器可以模拟在一系列的数据访问中cache的命中、不命中与牺牲行的情况,其中,需要牺牲行时,用LRU替换策略进行替换。
cache模拟器需要能处理一系列如下的命令:
Usage: ./csim-ref [-hv] -s -E
其中各参数意义如下:
①-h:输出帮助信息的选项;
②-v:输出详细运行过程信息的选项;
③-s:组索引的位数(意味着组数S=2^s);
④-E:每一组包含的行数;
⑤-b:偏移位的宽度(意味着块的大小为B=2^b);
⑥-t:输入数据文件的路径(测试数据从该文件里面读取)。
traces文件夹里面包含五个文件:
这五个文件就是用于测试csim.c的输入文件,各个文件中包含了各种不同指令,用于测试hits、missses、evictons。
trace文件中的指令具有如下形式:
I 0400d7d4,8
M 0421c7f0,4
L 04f6b868,8
S 7ff0005c8,8
即每行代表一个或两个内存访问。每行的格式是
[空格]操作 地址,大小
操作字段表示存储器访问的类型,其中:
“I”表示指令加载,
“L”表示数据加载,
“S”表示数据存储,
“M”表示数据修改(即数据存储之后的数据加载)。
每个“I”前面都没有空格。每个“M”,“L”和“S”之前总是有空格。
地址字段指定一个32位的十六进制存储器地址。
大小字段指定操作访问的字节数;
通俗地解释一下各种操作:
①对于‘I’指定地操作,实验说明中提到,我们不需要考虑:
意思就是valgrind运行地时候第一个指令总是为操作‘I’。
②对于‘L’以及‘S’指定的操作,我们简单地可以认为这两个操作都是对某一个地址寄存器进行访问(读取或者存入数据);
③对于‘M’指定的操作,可以看作是对于同一地址连续进行‘L‘和’S‘操作。
以yi.trace中的数据为例:
其解释如下:
①对于地址0x10进行访问:
0x10=0000...00010000,偏移值为最低四位,故S=1;
访问结果为mis;
②连续对地址0x20进行连续两次访问:
0x20=000...00100000,S=2;
结果为第一次mis,第二次hit;
③对地址0x22进行访问:
0x22=000...00100100,S=2;
由于操作②以将该块存入高速缓存,故结果为hit;
④对地址0x18进行访问:
0x18=000...00011000,S=1;
由于操作①以将该块存入高速缓存,故结果为hit;
⑤对地址0x110进行访问:
0x110=0...000100010000,S=1;
虽然操作①使得第一组(只有一行有效),但是这里的标志位的值Tag为1
故结果为先mis,后eviction;
⑥对地址0x210进行访问:
0x210=0...001000010000,S=1;
同操作⑤,但是这里的标志位的值为2,不匹配
故结果为先mis,后evicton;
⑦对地址0x12进行连续两次访问:
0x12=000...00000010010,S=1;
由于标志位不匹配,故第一次访问时mis,并evicton
第二次访问时当然就是hit。
上述分析也就是解释了实验说明中的示例:
(Ⅰ)数据成员声明:
实验指导中说到:
但是考虑到Cache的结构特别像多维数组,故这里简单地用一个动态开辟地三维数组表示,结构体中还有数据成员s-组数、E-每组的行数、b-块的字节数,如下:
其中数组的第一维表示组数,第二维表示每组的行数,第三维就是每行的三个值有效位,标志位以及LRU值。
(Ⅱ)各种操作的实现:
定义了数据成员以后,需要对数据进行各种操作(设值、申请内存、释放内存,查询数据等等),如下:
上图中实现的操作有:
Set_Cache():s、E、b的设置;
Get_Valid():返回第_s组第_E行的有效位;
Get_Tag():返回第_s组第_E行的标志位;
Get_Lru():返回第_s组第_E行的LRU计数值;
Get_Cache():动态为数组分配内存;
Free_Cache():释放内存。
接下来是:
上图中实现的操作有:
Is_Hit():判断是否命中——命中返回命中的行号,否则返回-1;
Get_Min_Lru():查询LRU值最小的行号;
Replace():替换第_s组中的第_E行,用于替换的标志位为Tag;
写完各种操作以后,我们还能想到什么——查看Cache中的内容,这样便可以实时地查看Cache中的情况,如果有错误通过查看Cache内容也能比较容易地找出错误出现的位置,如下:
第一个是输出每一组的信息,第二个函数是输出每一个组(整个Cache的信息);
前面还只是对Cache进行声明以及操作的实现,还没有具体的应用,要具体地用起来,当然要用户输入请求,然后再去用各种操作实现请求,所以接下来写输入的处理。输入的处理可以分为两个阶段来写:一个是命令行输入的解析,另一个个是测试数据的输入。下面分开来分析:
(Ⅰ)首先是命令行输入的解析:
命令行的输入就是再终端输入的一系列命令和选项,我们就是根据这些选项来设置各种值以及测试数据的文件位置的路径,比如:
./scim -s 4 -E 1 -b 4 -t traces/yi.trace,就是将s的值设为4,E的值设为1,b的值设为4,文件的路径为traces/yi.trace。
具体的选项实验说明中也有解释:
我们要做的就是将输入的这样的一串字符分析,得到具体的信息并利用。
把输入当做字符串来储存,然后再慢慢地分析未尝不是一个解决方法,但是这里有更加便捷的方法,即调用库函数getopt()函数来解析。实际上作者也建议我们这么做:
该函数的具体细节这里不做过多赘述,这里做一个简单地说明:
①首先调用这个函数需要上面提到的三个头文件;
②函数需要一个选项字符串作为参数,选项字符串即由各种选项构成的一个字符串,而选项就是上面提到的-s -E -b之类的了,所以考虑所有的选项,这里的选项字符串为“-hvs:E:b:t:”。
③然后函数会根据选项字符串去和你的输入进行匹配,当某个选项匹配时,函数会返回该选项对应的字符,而该选项对应的参数会存入变量optarg中,该变量是包含在头文件中的,这样一来的话我们就可以快速而简洁地分析命令行中地字符串了。
实现如下:
说明几点:
①这里用到了库函数atol(),即把一个字符串转化为一个整数;
②当解析选项字符串失败或者参数不合法时,应该要打印提示信息,即Print_Help():
有了这一部分输入以后,我们便可以验证我们对于Cache的创建及初始化的正确性了,输入如下:
至此Cache的创建于初始已结束。
(Ⅱ)测试数据的输入:
这里用于测试的数据已经由文件给定,我们要做的就是从文件中读取。从文件读取用到了库函数fscanf(),该函数与普通的scanf的区别就是该函数多一个参数,就是指向目标文件的文件指针,也就是从指向的文件读取数据。写法如下:
说明:这里的操作类型“I、L、S、M”虽然只是一个字符,但是这里用的是字符串输入,字符串输入比字符输入不容易出错。至此数据的输入已经完成。
定义了Cache以及有了测试数据以后我们就可以开始用Cache来模拟了,我们可以先理一下Cache工作的流程:
1、输入数据指定需要访问的地址寄存器。
2、分析输入的地址,并判断是否命中。
3、如果命中,则hits++,并更新LRU值。
4、如果不命中,则misses++,然后判断是否需要eviction,并更新LRU值。
下面进行逐条分析:
(Ⅰ)数据的输入由fscanf()处理。
(Ⅱ)地址的分析目的就是要得到指定的组数S以及对应的标志值Tag。计算如下:
因为Tag就是地址的高(m-s-b)位,同理可以分析S的产生。
(Ⅲ)接下来就是3和4,这里用一个函数来处理,并调用了对Cache的各种操作来完成:
解释如下:
第91行:判断是否命中,命中则返回命中第S组中的行数,否则返回-1;
第92~96行:如果没有命中,先++miss,然后按照LRU替换策略进行替换;
第97~101行:如果命中,则替换命中的行,其实这里没有必要替换,主要是要更新LRU值。
主函数中调用如下:
最终调用printSummary()进行结果的输出,并释放内存:
测试运行结果如下:
结果正确,至此所有的输入数据均已通过,Part A完成!
(Ⅰ)任务:
①编写一个实现矩阵转置的函数。即对于给定的矩阵A[N][M],得到矩阵B[M][N],使得对于任意0<=i
②在如下函数里面编写最终代码:
char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N]);
(Ⅱ)测试用例:
用三种不同规模的数组进行测试,规模分别为:
• 32 × 32 (M = 32, N = 32)
• 64 × 64 (M = 64, N = 64)
• 61 × 67 (M = 61, N = 67)
(Ⅲ)细节说明:
①可以编写多个函数来比较不同版本函数的性能,每个函数用如下方式进行注册:
registerTransFunction(transpose_submit, transpose_submit_desc);
将transpose_submit替换为函数名即可。
②在test-trans运行后会生成包含函数运行过程中的所有操作(L、S)的文件trace.fi,通过使用Part A中的模拟器以及该文件可以对函数运行的过程进行跟踪分析。
(Ⅰ)示例函数运行结果(以M32N32为例):
①输入命令./test-tran -M 32 -N 32运行:
②同时可以看到生成了包含所有操作的文件trace.fi,我这里注册了四个函数,所以生成了五个文件如下:
③通过查看trace.f4的内容,可以发现其为函数运行过程中的所有操作:
④通过使用Part A中的csim模拟器并用-v选项输出过程:
这里由于32×32的操作数过多,不好直接进行分析,下面以4×4规模的矩形进行具体分析。
(Ⅱ)具体实例分析(以4×4的矩阵为例):
①运行得到结果:
可以看到示例函数的misses数较多,下面进行misses过多的原因进行分析。
②为了更好地分析cache的命中与不命中的结果,我们将数组中元素占用高速缓存块的情况标志出来,由于cache的块的大小为32个字节,即8个int型数据,故数组中前8个元素会在用一个块中,后8个在另外一个块中,如下图:
其中的箭头为两个数组的访问数组的顺序:A数组按行访问,B数组按列访问。
④结合模拟器跟踪结果以及上图进行具体的分析:
1、A数组访问A[0][0],冷不命中,将块11装入cache。
2、B数组访问B[0][0],虽然B[0][0]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组B对应的块11装入cache。
3、A数组访问A[0][1],虽然A[0][1] 所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组A对应的块11装入cache。
4、B数组访问B[1][0],虽然B[1][0]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组B对应的块11装入cache。
5、A数组访问A[0][2],虽然A[0][2]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组A对应的块11装入cache。
6、B数组访问B[2][0],B[2][0] 所映射的块12不在cache中,冷不命中,将数组B对应的块12装入cache。
7、A数组访问A[0][3],A[0][3]所映射的块11在cache中,且标记位相同,故命中。
8、B数组访问B[3][0],B[3][0]所映射的块12在cache中,且标记位相同,故命中。
9、A数组访问A[1][0],A[1][0]所映射的块11在cache中,且标记位相同,故命中。
10、B数组访问B[0][1],虽然B[0][1] 所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组B对应的块11装入cache。
11、A数组访问A[1][1],虽然A[1][1]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组A对应的块11装入cache。
12、B数组访问B[1][1],虽然B[1][1]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组B对应的块11装入cache。
13、A数组访问A[1][2],虽然A[1][2]所映射的块11在cache中,但是标记位不同,造成冲突不命中,重新将数组A对应的块11装入cache。
14、B数组访问B[2][1],B[2][1] 所映射的块12在cache中,且标记位相同,故命中。
15、A数组访问A[1][3],A[1][3]所映射的块11在cache中,且标记位相同,故命中。
16、B数组访问B[3][1],B[3][1]所映射的块12在cache中,且标记位相同,故命中。
17、剩余的操作以同样的方法可以分析得出类似的过程。
⑤由以上分析可以看出miss过多的原因是在访问两个数组的过程中存在太多的冲突不命中,而造成传统不命中的原因是B数组与A数组中下标相同的元素会映射到同一个cache块,如上述的2~6步骤,就是不断地发生了冲突不命中。
⑥改善方法:
由以上地分析可以想到,要减少miss数,自然就是要解决冲突不命中的问题。冲突不命中实际上就是在访问同一个块中的两个元素的时候,由于中间访问了其它的块,导致已经加载的块被驱逐,进而第二次访问时不命中。基于这个原因,我们可以一次性访问同一个块中的多个元素,访问完以后便不再需要访问这个块了,从而可以大大地减少冲突不命中的数目。这里前8个元素在同一个块中,我们可以直接将这8个元素取出来,然后这8个元素所在的块便不再需要访问了,具体代码如下:
第27、28行就是取A数组中的前两行元素;
第30、31行就是对于A数组中前两行进行转置;
第33、34行就是取A数组中的后两行元素;
第36、37行就是对于A数组中后两行进行转置;
最终运行结果如下:
miss数由22减少为了8,显然有了一个非常大的提升,当然还有优化的空间,但这里不再展开。
(Ⅰ)分析矩阵中各元素所在块的情况:
同4×4的分析方法,我们分析各个元素所在块的情况,并将其标记:
由于每个块可以存4个int型的数据,cache一共有32个块,故对于32×32的矩阵而言,每一行的32个元素占4个组,每8行会占满整个cache,如下:
其中每一个格子代表一个块,即数组中的8个元素,每个格子中的数字表示组号,从0到31。
(Ⅱ)矩阵转置后元素的分布情况:
对于转置后的矩阵,每个元素的所在的位置与之前相比会关于矩阵的对角线对称,故转置后的情况可以由下图表示:
其中颜色相同的两个区域表示这两个区域的元素存在对应关系。
这里可能会有疑问,为什么标记对应区域的时候要标记8×8的区域呢?我们看到(Ⅰ)中的图,可以发现,以8×8来标记区域,那么两个区域所在的组是没有交集的,这也就意味着在这两个区域进行元素的转置时并不会发生冲突不命中的情况。而如果对于A数组直接按行访问,B数组直接按列访问就会得到大量的冲突不命中。下面展示这两种情况的具体结果。
(Ⅲ)转置过程中元素的访问情况:
(Ⅳ)初步运行结果:
①A数组直接按行访问,B数组直接按列访问:
代码:
运行结果:
可以看到有大量的miss数。
②将数组进行8分块:
代码:
运行结果:
相比第一种情况而言miss已经有了十分明显的减少,但是依然达不到最优的要求(miss<300)。
(Ⅴ)继续优化(下面的分析比较繁琐,可选择性跳过):
显然上面我们并没有考虑处于对角线上的区域(未涂色部分),而其余区域的不命中数已经达到了下限,即每一个块恰好不命中一次,所以我们接下来主要分析对角线上的区域。
由于对角线的元素转置以后的位置任然在同一个区域,所以很可能会有大量的冲突不命中,这十分类似于最前面分析的那个4×4的例子。所以我们同样根据4×4规模中的优化方法,即每次取一个块中的所有元素,取完以后便不再访问这个块来减少冲突不命中。
代码如下:
运行结果如下:
这样优化以后,miss数达到了287,已经满足最优的要求了。
(Ⅵ)结果分析:
接下来我们对具体的每一个元素的命中情况进行分析、记录如下表:
A数组中各个元素的命中情况
m-不命中、h-命中
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
B数组中各个元素的命中情况
m-不命中、h-命中
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
m |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
m |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
m |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
h |
m |
h |
h |
h |
h |
h |
h |
m |
可以知道:
对于A数组而言,每个块中有且只有一个元素不被命中,而且这也是无法避免大的。
对于B数组而言,,每个块中不仅仅只有一个元素不被命中,处于对角线上的元素也会不被命中。
而我们当然期望对于B数组的访问也能同A数组一样,每个块中只有一个元素不被命中,下面继续进行对角线上的优化处理。
(Ⅶ)处理对角线:
冲突不命中的关键在于:在访问了一个块之后,这个被加载的块由于又被访问从而使得这个块被覆盖,导致第二访问这个块的时候不命中,如这里的第二行的第二列的不命中,由于将第一行的数据进行转置时会访问到B数组的第二行,而随后A数组也会访问到第二行,接着B又会重新访问第二行,这个第二次访问第二行的过程就是一次冲突不命中。按照这个访问过程来讲,要避免这个不命中,有两种方法:
①在A数组访问第二行之前B数组不访问第二行。
②在A数组访问第二行之后B数组不访问第二行。
显然第二种方法是行不通的,毕竟B[1][1]=A[1][1],也就是要先访问A数组的A[1][1]才能得到B数组的B[1][1]。
所以接下来我们尝试实现第一种:
我们在访问完A数组的第一行的元素以后,按计划是直接将所有元素转置到B数组中,但是这样一来的话,我们就必须访问B数组中的第二行。所以这里我们换一种思路,是否可以先不把数据转置,等到A数组中第二行的元素被访问了再转置。
这种思路的一个问题是,如果不直接接下转置,那么A数组中第一行的的8个元素存到哪里呢(注意我们最多定义12个变量,而for循环中占了4个,所以最多只能再用8个)。显然不能用变量存了,但是我们注意到,B数组中大的第一行在不命中一次以后,对于这一行的访问以后一定是命中的,因为A数组不会再访问第一行了,所以我们可以将A数组第一行的元素存入B数组的第一行,然后
将A数组中的第二行读取以后再访问B数组中的第二行,这样一来就实现了第①种方法。
依着这种思路,我们可以同样地处理其余行,使得在对B数组的第i行进行访问时,A数组种的第i行已经被访问,以后A数组不会再访问第i行。
按照这个思路可以得到如下代码:
else if(N==32&&M==32){
for(i=0;i
我们再看一下运行结果:
不命中数为259。这里有一个小问题——我们期望是每个块恰好只有一个元素不被命中,那么不命中数应该是32×4×2=256,这里不
命中数为259是因为在函数调用过程中有其它开销,在跟踪使用模拟器跟踪输出时可以看到,如下:
那额外的三次不命中就是这三次多余的开销产生的。
(Ⅷ)其它分块:
上面分析的是8分块的情况,同样的,我们也可以进行4分块,看一看实际效果,代码如下:
测试结果如下:
可以看到最终的结果虽然相比于最初始的数据表现地很好,但是比起上面地8分块还是有一定地差距,因为4分块并没有充分利用被访问过地块,导致一个需要被多次加载,产生冲突不命中。
其它的分块情况,也能得到类似的结论,因为通过上面的分析,8分块已经使不命中数达到了下限。
(Ⅰ)分析矩阵中各元素所在块的情况:
同以上的分析方法,我们分析各个元素所在块的情况,并将其标记:
对于64×64的矩阵而言,每一行的64个元素占8个组,故每4行会占满整个cache,如下(前32行):
其中每一个格子代表一个块,即数组中的8个元素,每个格子中的数字表示组号,从0到31。
(Ⅱ)基于上面M32×N32的8分块情况,我们在这里同样分析进行8分块的命中情况。
以上图中的两个红色区域为例:
对A数组按行进行访问时,由于两个红色区域之间没有相同的块,所以每个块只有一次不命中。
而对B数组按列进行访问时,可以看到前4行和后4行所映射的块是相同的,这就会导致一个特别糟糕情况:
①再访问完前4行的第一列以后,访问后4的第一列行时,由于冲突不命中,会导致原来的块被驱逐。
②接着再访问前4行的第二列时,由于原来的块已经被驱逐,这里又会导致冲突不命中,并将后4行的块驱逐。
③这样在访问后4行的第二列时又会产生冲突不命中。
如此反复下去,最终B数组访问的区域中所有的元素均会不命中。
我们来看一下8分块具体的测试结果:
代码:
测试结果:
相比原始的4723的miss数,可以说基本上没什么改进,还得分析其它的分块情况。
(Ⅲ)4×4分块的分析:
考虑到8×8分块的miss数没什么改进的原因是由于前4行和后4行反复的冲突不命中,基于这个原因,我们容易想到对矩阵进行4分块,也就是先将前4行先转置完成,再处理后4行。
代码如下:
测试结果如下:
得到miss数为1699,有一个非常大的提升了,但是对于最优的要求1300还有一点差距,下面继续进行不命中的分析:
由于一个块的大小为8个int型数据,所以我们按照4×4来分块还是没有充分利用每次加载以后的每个块。具体还是表现在对B数组的访问。
由于是进行4分块,所以对于每一个高速缓存块都会进行两次访问,对于A数组而言两次访问的间隔不会出现将原来的块覆盖的情况。而对于B数组而言,由于访问顺序为:前4行的前4列->后4行的前4列->前4行后4列->后4行的后4列。
由于后4行的前4列所在的块会覆盖前4行的前4列的块,所以在后面的两次访问均会又有一次不命中,所以对于B数组每个块而言,都会有两次不命中,而对于8×8分块而言,每个块都会不命中8次,这是4×4分块的优化之处,而两次的不命中也是不足之处。
下面尝试进行再次优化。
(Ⅳ)提高数组B的命中:
上面分析到数组B中每一个块中都会有两个元素不被命中(除了位于对角线区域上的块,更多),而不命中中原因是两次访问间隔中间有使原来的块被驱逐的访问,这让我们容易想到M32×N32规模中对角线上元素的不命中,这两种情况是否类似,那么这里我们也用类似的方法处理:
①我们看到两个对应的8×8的区域:
这两个红色区域也就是对应的区域,下面我们一步步将右上角(区域一)的元素转置到左下角(区域二)。
②我们将区域一的前4行全部存入区域二的前4行,这个过程中前4行的前4列已经转置完成(黄色区域),但是对于前4行的后4列还没有放入应该放的位置(蓝色区域),但是为了不再访问同一个块,我们同时将数据取出,存入还没有用到的区域中。:
③逐行进行后4行前四列的转置,其过程如下所示:
至此后4行前4列也已转置完成,最后再进行后4行的后4列:
④后4行后4列:
至此这个8×8区域已完成转置,那么命中情况如何呢,下面进行分析(B数组):
步骤1:B数组访问前4行;
步骤2:B数组逐行访问前4行后4列与后4行前4列;
步骤③:B数组逐行访问后4行后4列。
按照这个顺序访问以后,显然对于B数组中的每一个块的元素,我们只会有一个不命中,达到了预期的要求。
(Ⅴ)优化后的结果:
代码:
运行结果:
不命中数已经下降到了1300以下,满足最优的要求了。
(Ⅵ)小结:
对于上面的分析,无论是4×4分块还是8×8分块的情况,都是在不同的区域上讨论的,而对于对角线上的区域其命中情况显然会有所不同,这也是这里的不命中数没有预期设想的结果,即64×8×2=1024,多余的部分也就是对角线上的区域产生的,按照M32×N32中的思路,对角线上的情况显然是可以优化的,但是代码的可读性会大大降低,并且写起来也会十分繁琐,故这里不再做进一步讨论。实际上现在的优化结果表现已经足够好了。
(Ⅰ)矩阵中个元素所在块的情况分析:
这里由于矩阵的规模为61×67,所以对于各个元素所在块的规律会较之前有所不。对于之前的32×32以及64×64,由于每一行的元素个数恰好为8的倍数,所以每一行恰好会占满整数个块,那么在编写代码的过程中就很容易利用这一特性。而对于61×67规模而言,这里每一行不再满足这样的规律。例如第一行的最后面5个元素和第二最前面3个元素是在同一个块的。由于这个特性,对于整个矩阵而言,各个元素所在块的情况就变得不好处理。
(Ⅱ)尝试进行各种分块:
基于之前得经验与分析,我们可以看到将矩阵进行分块处理以后,不命中数总会或多或少地减少,这里我们直接进行各种大小规模的分块处理,观察其运行结果。
①4×4分块代码:
测试结果:
②8×8分块代码:
测试结果:
③16×16分块代码:
测试结果:
④用表格记录各种大小规模的分块结果如下:
分块规模N×N |
miss数 |
分块规模N×N |
miss数 |
2×2 |
3115 |
12×12 |
2057 |
3×3 |
2648 |
13×13 |
2048 |
4×4 |
2425 |
14×14 |
1996 |
5×5 |
2296 |
15×15 |
2021 |
6×6 |
2224 |
16×16 |
1992 |
7×7 |
2152 |
17×17 |
1950 |
8×8 |
2118 |
18×18 |
1961 |
9×9 |
2092 |
19×19 |
1979 |
10×10 |
2076 |
20×20 |
2002 |
11×11 |
2089 |
21×21 |
1957 |
可以看到,但分块规模>=4时,不命中数miss在2000左右浮动,实验结果表明当分块大小为17×17miss数最少(在当前的写法下)。
(Ⅲ)小结:
由上面的代码可以看出,这里的优化只是一种很粗糙的做法,仅仅是进行简单的分块处理,并没有做其它的分析与优化处理,但是最好情况下的结果已经达到了最优要求(<2000),所以这里也不再进一步分析了。
运行命令:./driver.py
Part B中的三个测试用例均获得满分,Part B完成!
对于Part A来说,只需要实现一个模拟的过程,总体上难度不是很大,主要是对于整个模拟过程要有一个清晰的思路,即要做什么,怎么做,明白了每一步之后编写代码也就水到渠成了。通过这个部分学会了更多读入的处理,具体为:
①用getopt()函数处理命令行输入的参数,自己写一个读入当然也是可以的,但是写起来自然没有现成的来的块与标准。getopt()的参数中有来自于main()函数的中的参数,这也是第一次使用main()中的参数。
②用fscanf()从文件读取数据,以前也写过其它从文件读取的方式,例如freopen(),这里用fscanf()进行输入,又学到了新的方法。
另外一个也锻炼了代码能力,上面其实也有分析到,最后面写完以后发现有部分结果不对,这个时候怎么对代码进行纠错也考验了代码能力。通过一步步分析可能出错的地方,一步步输出验证可以提高对代码的读写能力。
这个实验Part B才是真正需要动脑筋的地方,从一个4×4的例子出发,分析其命中与不命中的情况,再引申到其它特殊情况,最后再到一般情况。对于一个问题,需要不断思考怎么解决、怎么优化、是否还有优化的可能等等,在这一系列思考的过程中,锻炼我们的思维能力以及解决实际问题的能力。
另外一个收获是这个部分使我对cache的工作细节有了一个更深刻的理解。再上一个实验perflab程序性能优化实验中,还只知道分块技术可以提高命中率,提高程序性能,但是还不是特别清楚其原理,以及块的大小对命中率的影响也不是很清楚,只能多次尝试,并不会具体分析。在这个部分就对这个知识点进行了深入的分析与理解,彻底掌握了分块与命中率的提高的关系。
Part A&&Part B
****************************************************************************************************************************************
写完博客以后发现发表的文章和写时的文章换行和缩进不统一,有没有路过的大佬教教我怎么解决啊,万分感谢!