Cachelab partA
这是该实验的 partA 部分,主要是用 c 语言模拟 cpu 对cache的存取过程,以及其缓存命中,不命中和不命中时的替换的情况
实验准备
实验的环境在 Linux
具体的实验包可以去 CMU 的csapp实验官网下载
实验
同时该实验 CMU 有相应的辅助材料可以利用
实验指导PPT
解答
本实验就是简单模拟缓存的行为
注意
- 本实验需要根据传入的不同的 s , E , b s, E, b s,E,b 值进行模拟
- 替换策略使用LRU(最近最少使用)策略
数据结构
所以根据此可以创建一个存储缓存行的结构体
typedef struct CACHE_LINE {
short valid; // 有效位
unsigned tag; // 标记
int stamp; // 模拟时间戳,用于lru
}cache_line, *cache_asso, **cache;
同时定义该结构体的指针和指针的指针,便于后续 malloc 二维数组来模拟 cache
具体思路
那么结构体有了,我们需要做的就是根据给出的 s , E , b s, E, b s,E,b 的值 malloc 出二维数组
有了二维数组,我们就可以读取每一条指令,将指令的地址字段解析出 标记 和 组索引
根据组索引和标记模拟命中情况,记录命中和不命中,替换的次数
替换策略采用简单版的LRU,具体算法如下:
- 对于每一次缓存动作,将定位的块中所有的缓存行的 stamp + 1,模拟时间推移
- 对命中的缓存行,stamp = 0
- 替换掉 stamp 值最大的缓存行
代码
#include "cachelab.h"
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
typedef struct CACHE_LINE {
short valid;
unsigned tag;
int stamp;
}cache_line, *cache_asso, **cache;
cache _cache_ = NULL;
int s, S, E, b, v;
char t[1024];
int hits, misses, evictions;
int init_cache();
void free_cache();
unsigned get_block_index(unsigned address);
unsigned get_tag(unsigned address);
void visit_cache();
int hit_cache(unsigned index, unsigned tag);
int main(int argc, char **argv)
{
int opt;
while((opt = getopt(argc, argv, "vs:E:b:t:")) != -1) {
switch(opt) {
case 'v':
v = 1;
break;
case 's':
s = atoi(optarg);
break;
case 'E':
E = atoi(optarg);
break;
case 'b':
b = atoi(optarg);
break;
case 't':
strcpy(t,optarg);
break;
default:
printf("wrong arg!");
break;
}
}
if(s <= 0 || E <= 0 || b <= 0 || t == NULL) return -1;
S = 1 << s;
init_cache();
visit_cache();
free_cache();
printf("hits:%d misses:%d evictions:%d\n",hits,misses,evictions);
printSummary(hits,misses,evictions);
return 0;
}
int init_cache() {
_cache_ = (cache)malloc(sizeof(cache_asso) * S);
if(_cache_ == NULL) return -1;
for(int i = 0;i < S;i ++) {
_cache_[i] = (cache_asso)malloc(sizeof(cache_line) * E);
if(_cache_[i] == NULL) return -1;
for(int j = 0;j < E;j ++) {
_cache_[i][j].valid = 0;
_cache_[i][j].tag = -1;
_cache_[i][j].stamp = 0;
}
}
return 1;
}
void free_cache() {
if(_cache_ != NULL) {
for(int i = 0;i < S;i ++) {
if(_cache_[i] != NULL) free(_cache_[i]);
}
free(_cache_);
}
}
// 解析缓存块
unsigned get_block_index(unsigned address) {
return address >> b & ((1 << s) - 1);
}
// 解析缓存行
unsigned get_tag(unsigned address) {
return address >>= (b + s);
}
// 模拟缓存动作
void visit_cache() {
FILE *f;
f = fopen(t, "r");
char ident;
unsigned address;
int size;
int status;
while(fscanf(f," %c %x,%d",&ident, &address, &size) > 0) {
unsigned index = get_block_index(address);
unsigned tag = get_tag(address);
switch(ident) {
case 'I':
continue;
case 'S':
case 'L':
status = hit_cache(index,tag);
if(status == 0) printf("%c %x,%d hit",ident,address,size);
else if(status == 1) printf("%c %x,%d miss",ident,address,size);
else printf("%c %x,%d miss eviction",ident,address,size);
puts("");
break;
case 'M':
status = hit_cache(index,tag);
hits ++;
if(status == 0) printf("%c %x,%d hit hit",ident,address,size);
else if(status == 1) printf("%c %x,%d miss hit",ident,address,size);
else printf("%c %x,%d miss eviction hit",ident,address,size);
puts("");
break;
}
}
fclose(f);
}
/*
模拟命中情况
0 hit
1 miss
2 miss eviction
*/
int hit_cache(unsigned index, unsigned tag) {
cache_asso cache_block = _cache_[index];
// update lru
for(int i = 0;i < E;i ++) cache_block[i].stamp ++;
// hit
for(int i = 0;i < E;i ++) {
if(cache_block[i].valid && cache_block[i].tag == tag) {
cache_block[i].stamp = 0;
hits ++;
return 0;
}
}
// miss
for(int i = 0;i < E;i ++) {
if(!cache_block[i].valid) {
cache_block[i].valid = 1;
cache_block[i].tag = tag;
cache_block[i].stamp = 0;
misses ++;
return 1;
}
}
int idx = 0;
int maxv = 0;
for(int i = 0;i < E;i ++) {
if(cache_block[i].stamp > maxv) {
maxv = cache_block[i].stamp;
idx = i;
}
}
cache_block[idx].tag = tag;
cache_block[idx].valid = 1;
cache_block[idx].stamp = 0;
misses ++;
evictions ++;
return 2;
}
结果
满分27分
总结
partA 部分断断续续花了挺长时间才做完,在学校学 csapp 时间实在是太零碎了,但是一开始很迷茫,看了一下实验指导PPT以及一些细小的地方参考了其它博客,就逐渐清晰了,写代码就很快了。
写完之后测过几个 trace 就去测试了,一遍过了,还是挺有成就感的