csapp lab5 cache_lab记录(partA)

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;
}

部分思路借鉴:csapp实验5-cachelab实验详解_cache lab-CSDN博客

代码参考:CSAPP | Lab5-Cache Lab 深入解析 - 知乎 (zhihu.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值