1.文档简要说明
Part A 要求在csim.c
下编写一个高速缓存模拟器来对内存读写操作进行正确的反馈。这个模拟器有 6 个参数:
Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>
• -h: Optional help flag that prints usage info
• -v: Optional verbose flag that displays trace info
• -s <s>: Number of set index bits (S = 2s is the number of sets)
• -E <E>: Associativity (number of lines per set)
• -b <b>: Number of block bits (B = 2b is the block size)
• -t <tracefile>: Name of the valgrind trace to replay
其中,输入的 trace 的格式为:
[space]operation address, size
,operation 有 4 种:
I
表示加载指令L
加载数据S
存储数据M
修改数据
模拟器不需要考虑加载指令,而
M
指令就相当于先进行L
再进行S
,因此,要考虑的情况其实并不多。模拟器要做出的反馈有 3 种:
hit
:命中,表示要操作的数据在对应组的其中一行miss
:不命中,表示要操作的数据不在对应组的任何一行eviction
:驱赶,表示要操作的数据的对应组已满,进行了替换操作
2.代码以及实现思路说明
1. 首先是定义cache以及cache块的结构
typedef struct cache
{
int S;//组数
int E;//行/块数
int B;//字节数
Cache_block **block;//地址
}Cache;
typedef struct cache_block
{
int tag;//标志位
int valid;//有效位
int time_tamp;//时间戳
}Cache_block;
2. 初始化cache以及cache块
void InitCache(int s,int E,int b)
{
int S=1<<s;//左移一位相当于乘二,在计算高速缓存的大小和每行的字节数时
//通常采用的是以2为底的对数计算方式。
//这样计算出来的S和B才能符合实际的内存组织结构
//以便后续的内存分配和操作能够正确进行。
int B=1<<b;
Cache->S=s;
Cache->B=b;
Cache->E=E;
Cache->block=(Cache_block **)malloc(sizeof(Cache_block *)*S);//为Cache分配地址(即所有组)
for(int i=0;i<S;i++)
{
Cache->block[i]=(Cache_block *)malloc(sizeof(Cache_block)*S);//为每一组分配地址
for(int j=0;j<E;j++)//初始化每组的每一行
{
Cache->block[i][j].valid=0;//初始时,高速缓存是空的
Cache->block[i][j].tag=-1;
Cache->block[i][j].time_tamp=0;
}
}
3. 现在我们有了一个cache
我们应当根据cache地址去查找需要的块是否存在,再进行后续操作。该函数遍历该组中所有块,只有v=1且tag相同,那就hit,返回这个块的索引即可。
否则miss,且返回-1
int GetIndex(int op_set,int op_tag)//op_set代表组数,op_tag代表标志位
{
for(int i=0;i<Cache->E;i++)
{
if(Cache->block[op_set][i].valid&&Cache->block[op_set][i].tag==op_tag)
{
return i;
}
}
return -1;
}
4. 如果miss,则要排除是否全满这一特殊情况。
如果没有全满,则选择第一个有效位为0的块作为替换块,返回该索引
如果全满,则返回-1
int isFull(int op_set)
{
for(i=0;i<E;i++)
{
if(Cache->block[op_set][i].valid==0)
{
return i;
}
}
return -1;
}
5. 假如没有全满,则设置更新策略, 更新isFull()中返回的索引块
LRU的实现策略是更新的这一块的时间戳设置为0,同组中其他有效位位1的块时间戳+1,
这样做代表该组中时间戳最大的块为最久没有访问的块
void Update(int i,int set_op,int op_tag)
{
Cache->block[set_op][i].valid=1;
Cache->block[set_op][i].tag=op_tag;
for(int k=0;k<Cache->E;k++)
{
Cache->block[set_op][k].time_tamp++;//每访问使用一次某个块,给该块的时间戳设为0,并将其他块的时间戳+1,时间戳越大说明越不常使用
}
Cache->block[set_op][i].time_tamp=0;//表示最近一次使用
}
ps:有同学问能不能直接设置每访问一个块就令该块的次数加1,以便优化时间复杂度。这样的方法其实更契合LFU,LRU还是更适合用本篇的代码来实现
6. 如果满了,那使用Update()实现LRU方法,找到某组中最久没访问的就是找有效位为1的哪一块时间戳最大,查找到该组的索引返回即可
然后再调用Update()
int findLRU(int set_op)
{
int max_index=0;
int max_stamp=0;
for(int i=0;i<Cache->E;i++)
{
if(Cache->block[set_op][i]>max_stamp)
{
max_tamp=Cache->block[set_op][i];
max_index=i;
}
}
return max_index;
}
7. 最后组装一下update,isfull,findurl三个函数为updateinfo函数即可
void UpdateInfo(int set_op,int set_tag)
{
int index=getIndex(op_set,op_tag);
if(index==-1)
{
mis_count++;
if(verbose)
{
printf("miss ");
}
int i=isFull(op_set);
if(i==-1)
{
eviction_count++;
if(verbose)
{
printf("eviction\n");
}
i=findLRU(op_set);
}
Update(i,op_set,op_tag);
}
else
{
hit_count++;
if(verbose)
{
printf("hit\n");
}
Update(index,op_set,op_tag);
}
}
8. free,申请了动态内存记得free一下。free内存不是指直接调用一个free函数就行了,这样会生成一个野指针,需要把该指针指向NULL,成为一个空指针
void FreeCache(void)
{
free(Cache);
Cache=NULL;
}
9. 最后一个函数是四种操作分别实现一下
void GetTrace(int s,int E,int b)
{
FILE *p_file;
p_file=fopen(t,"r");
if(p_file==NULL)
{
exti(-1);
}
char indentifier;
unsigned address;
int size;
// Reading lines like " M 20,1" or "L 19,3"
while(fscanf(p_file," %c %x,%d",&identifier,&address,&size)>0) // I读不进来,忽略---size没啥用
{
int op_tag=address>>(s+b);//一个内存地址分为三个部分:标记(Tag)、组索引(Index)和块内偏移(Block offset)。
int op_set=(address>>b)&(unsigned(-1)>>(8*sizeof(unsigned)-s));
//考虑先右移 b 位,再用无符号 0xFF... 右移后进行与操作将 tag 抹去。为什么要用无符号右移呢?因为C语言中的右移为算术右移,有符号数右移补位的数为符号位。
switch(identifier)
{
case 'M'://一次存储一次加载
updateInfo(op_set,op_tag);
updateInfo(op_set,op_tag);
break;
case 'L':
updateInfo(op_set,op_tag);
break;
case 'S':
updateInfo(op_set,op_tag);
break;
}
}
fclose(p_file);
}
10. 主函数,需要解析一下所有的输入参数
Cache *Cache = NULL;
int miss_count = 0;
int hit_count = 0;
int eviction_count = 0;
int verbose = 0;
char t[50];
extern char *optarg;
void InitCache(int s, int E, int b);
void Update(int i, int op_set, int op_tag);
int findLRU(int op_set);
int GetIndex(int op_set, int op_tag);
int isFull(int op_set);
void UpdateInfo(int op_set, int op_tag);
void GetTrace(int s, int E, int b);
void FreeCache();
int main()
{
char opt;
int s, E, b;
while (-1 != (opt = getopt(argc, argv, "hvs:E:b:t:")))
{
switch (opt)
{
case 'h':
printf("input format wrong");
exit(0);
case 'v':
verbose = 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("input format wrong!");
exit(-1);
}
}
initCache(s, E, b);
getTrace(s, E, b);
freeCache();
printSummary(hit_count, miss_count, eviction_count);
return 0;
}