我的逻辑是时间戳越大则表示该行最后访问的时间越久远。先看 LRU 更新的代码:
void update(int i, int op_s, int op_tag){ cache->line[op_s][i].valid=1; cache->line[op_s][i].tag = op_tag; for(int k = 0; k < cache->E; k++) if(cache->line[op_s][k].valid==1) cache->line[op_s][k].time_tamp++; cache->line[op_s][i].time_tamp = 0; }这段代码在找到要进行的操作行后调用(无论是不命中还是命中,还是驱逐后)。前两行是对有效位和标志位的设置,与时间戳无关,主要关注后几行:
遍历组中每一行,并将它们的值加1,也就是说每一行在进行一次操作后时间戳都会变大,表示它离最后操作的时间变久
将本次操作的行时间戳设置为最小,也就是0
由此,每次只需要找到时间戳最大的行进行替换就可以了:
int find_LRU(int op_s) { int max_index = 0; int max_stamp = 0; for(int i = 0; i < cache->E; i++){ if(cache->line[op_s][i].time_tamp > max_stamp){ max_stamp = cache->line[op_s][i].time_tamp; max_index = i; } } return max_index; } 缓存搜索及更新先解决比较核心的问题,在得知要操作的组op_s以及标志位op_tag后,判断是miss还是hit还是应该eviction调用find_LRU。
先判断是miss还是hit:
int get_index(int op_s, int op_tag) { for (int i = 0; i < cache->E; i++) { if (cache->line[op_s][i].valid && cache->line[op_s][i].tag == op_tag) return i; } return -1; }遍历所有行,如果某一行有效,且标志位相同,则hit,返回该索引。否则,miss,返回 -1。当接收到-1后,有两种情况:
冷不命中。组中有空行,只不过还未操作过,有效位为0,找到这个空行即可
所有行都满了。那么就要用到上面得 LRU 进行选择驱逐
所以,设计一个判满的函数:
int is_full(int op_s) { for (int i = 0; i < cache->E; i++) { if (cache->line[op_s][i].valid == 0) return i; } return -1; }扫描完成后,得到对应行的索引值,就可以调用 LRU 更新函数进行更新了。整体调用如下:
void update_info(int op_tag, int op_s) { int index = get_index(op_s, op_tag); if (index == -1) { miss_count++; if (verbose) printf("miss "); int i = is_full(op_s); if(i==-1){ eviction_count++; if(verbose) printf("eviction"); i = find_LRU(op_s); } update(i,op_s,op_tag); } else{ hit_count++; if(verbose) printf("hit"); update(index,op_s,op_tag); } }至此,Part A 的核心部分函数就编写完了,下面的内容属于是技巧性的部分,与架构无关。
指令解析设计的数据结构解决了对 Cache 的操作问题,LRU 时间戳的实现解决了核心的驱逐问题,缓存扫描解决了对块中哪一列进行操作的问题,而应该对哪一块进行操作呢?接下来要解决的就是指令的解析问题了。
输入数据为[space]operation address, size的形式,operation很容易获取,重要的是从address中分别获取我们需要的s和tag,address结构如下:
这就用到了第二章以及Data Lab的知识。tag 很容易得到,右移 (b + s) 位即可:
int op_tag = address >> (s + b);获取 s,考虑先右移 b 位,再用无符号 0xFF... 右移后进行与操作将 tag 抹去。为什么要用无符号 0xFF... 右移呢?因为C语言中的右移为算术右移,有符号数右移补位的数为符号位。
int op_s = (address >> b) & ((unsigned)(-1) >> (8 * sizeof(unsigned) - s));由于数据读写对于本模拟器而言是没有区别的,因此不同的指令对应的只是 Cache 更新次数的问题:
void get_trace(int s, int E, int b) { FILE *pFile; pFile = fopen(t, "r"); if (pFile == NULL) { exit(-1); } char identifier; unsigned address; int size; // Reading lines like " M 20,1" or "L 19,3" while (fscanf(pFile, " %c %x,%d", &identifier, &address, &size) > 0) // I读不进来,忽略---size没啥用 { //想办法先得到标记位和组序号 int op_tag = address >> (s + b); int op_s = (address >> b) & ((unsigned)(-1) >> (8 * sizeof(unsigned) - s)); switch (identifier) { case 'M': //一次存储一次加载 update_info(op_tag, op_s); update_info(op_tag, op_s); break; case 'L': update_info(op_tag, op_s); break; case 'S': update_info(op_tag, op_s); break; } } fclose(pFile); }update_info就是对 Cache 进行更新的函数,前面已经讲解。如果指令是M则一次存储一次加载,总共更新两次,其他指令只用更新一次,而I无需考虑。
命令行参数获取