实验要求
Writing a Cache Simulator
cache高速缓存用于实现CPU和MM之间的数据交互,起到一个缓存的作用,高速缓存技术大大增强了CPU的数据利用率。本次实验的内容是编写代码来模拟高速缓存的运作并且统计某一组指令执行后的hit ,miss,eviction个数。在这之前,我们先来了解cache的工作原理。
cache高速缓存工作原理
- 定位到组
- 检查组内任意行是否与标记匹配 如果匹配且行有效,则命中 ,若不匹配,旧行被替换,常用LRU规则
- 以偏移量定位数据
(2)写入 Cache,多重备份,直写回写,写分配非写分配
- 关于写cache我们要明白的是,数据是多重备份的,层次存储于L1, L2, 主存, 硬盘…
- 对于写命中包括两种写方式:直写 (立即将数据被写回到主存) Write-through ,写回 (延迟写回主存,直到数据被替换时才更新主存内容 ) Write-back 每个高速缓存行维护一个额外的修改位dirty bit
- 对于写不命中,存在两种分配方式:写分配 (加载相应的低一层中的块入高速缓存,然后更新高速缓存) Write-allocate 若空间局部性好,则性能好; 非写分配 (直接写到低一层的主存中) No-write-allocate
- 一般的写cache方式设定为Write-through + No-write-allocate 或 Write-back + Write-allocate两种配合。
- 简单的说MM主存中的一条地址,对应了cache中的一个位置。这条地址包括了tsb三个部分,tag是标识位,set是cache的组号,block是该组中某一行的块偏移量。
不命中分类:
- 冷不命中Cold (compulsory) miss:首次访问The first access to a block has to be a miss
- 冲突失效Conflict miss:要访问的地址上的数据的tag和MM地址中的tag不匹配,Conflict misses occur when the level k cache is large enough, but multiple data objects all map to the same level k block
- 容量失效Capacity miss:当整个组都被占用时发生容量失效,这个时候将会用LRU替换规则驱逐最近最少访问的行,Occurs when the set of active cache blocks (working set) is larger than the cache
替换规则LRU
当整个组都被占用时发生容量失效,这个时候将会用LRU替换规则驱逐最近最少访问的行。具体的LRU算法实现的过程是:
- 如果某一组的某一行发生了hit,那么这一行的lru值置为maxn
- 这一组所有的行的lru-1
- 如果发生驱逐将会替换掉最近最少访问的行,即lru最小
编写模拟器
(1)构造cache
const int Maxn=0x7fffffff-1;
int hit=0,miss=0,eviction=0;//要模拟的三个变量,命中,失效,冲突
//定义cache的结构
typedef struct{
int valid;//有效位
int tag;//标识位
int lruNumber;//LRU算法计数值
}Line;//cache line行
typedef struct{
Line *line;//一组中的所有行
}Set;//cache set 组
typedef struct{
int set_num;//组数
int line_num;//每组的行数
Set *sets;//整个cache的多个组
}Sim_Cache;
(2)内存的创建与释放
void putSets(Sim_Cache* cache)
{
printf("S=%d,E=%d\n",cache->set_num,cache->line_num);
int i,j;
for(i=0;i<cache->set_num;i++)
{
printf("set%d: ",i);
for(j=0;j<cache->line_num;j++)
{
//printf("Line%d ",j+1);
printf("<%d,%d,%x> ",cache->sets[i].line[j].valid,cache->sets[i].line[j].tag,cache->sets[i].line[j].lruNumber);
}
printf("\n");
}
}
void init_SimCache(int s,int E,int b,Sim_Cache *cache)//按照手册提示,初始化
{
int S=(1<<s);//2^s
cache->set_num=S;//初始化参数S
cache->line_num=E;//初始化参数E
cache->sets=(Set*)malloc(sizeof(Set)*S);//开辟组
int i,j;
for(i=0;i<S;i++)//开辟S行line
cache->sets[i].line=(Line*)malloc(sizeof(Line)*E);
for(i=0;i<S;i++)//开辟Line
{//初始化有效位,标识位,LRU位
for(j=0;j<E;j++)
{
cache->sets[i].line[j].valid=0;//有效位
cache->sets[i].line[j].tag=0;//标识位
cache->sets[i].line[j].lruNumber=0;//LRU位
}
}
// putSets(cache);
}
void free_SimCache(Sim_Cache *cache)
{
int i;
for(i=0;i<cache->set_num;i++) free(cache->sets[i].line);//释放行
free(cache->sets);//释放组
cache->line_num=0;
cache->set_num=0;
//printf("free the cache succeful!\n");
}
(3)命令解析函数
标准模拟器的Linux指令形式:
Linux> ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>
参数属性如下:
参数 | 属性 | 属性解释 | 可选性 |
---|---|---|---|
-h | h: Optional help flag that prints usage info | 可选的帮助标志,打印使用信息 | 可选 |
-v | Optional verbose flag that displays trace info | 显示跟踪信息的可选详细标记 | 可选 |
-s < s > | -s < s > Number of set index bits (S = 2^s is the number of sets) | 集合索引位的数目(S = 2^s集合的数目) | 必选 |
-E < E > | Associativity number of lines per set | 结合性,每组中的行数 | 必选 |
-b < b > | Number of block bits (B = 2b is the block size) | 块位数(B = 2^b为块大小) | 必选 |
-t< tracefile > | Name of the valgrind trace to replay | 回溯的valgrind跟踪文件的名称 | 必选 |
我们要编写get_opt函数来对输入的指令进行解析,这里采用的是getopt函数进行解析,linux的命令参数分长参数和短参数,而这个函数是用来且只能用来处理短参数的,函数的返回值即为当前调用它所读取到的那个参数(int对应其ASCII码值),其中的opstring是一个短参数集合的字符串。每一个字母后面如果加一个冒号代表其必须带有一个附加的参数,不带就是必须没有附加的参数,而如果有两个冒号则是可带可不带参数,还有一点很重要,字母的顺序是无所谓前后。
- 函数原型:
int getopt(int argc,char** argv,const char* opstring);
- opstring是一个短参数集合的字符串:
const char* optstring = "hvs:t:b:E:"
这个函数的调用要引用<getopt.h>库,该库还为我们提供了几个比较用帮助的全局变量,比较常见的有:
- optarg:当前解析的参数的附加参数
- opterror:解析是否出错(存在不能识别的opt即不在我们的optstring中,或者该加附带参数的opt没加不该加的加了)从而打印错误信息
- optin:下一个要解析的参数的index。我们可以根据自己的需要来利用并且手动的更改这些变量。
返回值: - optarg来获取每次解析参数所得到的附加参数,以字符串的形式返回,这就是解析的关键
int get_opt(int argc,char *argv[],int *s,int *E,int* b,char *tracefileName,int *isVerbose)
{
int opt;
while((opt=getopt(argc,argv,"hvs:E:b:t:"))!=-1){
switch (opt) {
case 'h'://打印帮助信息
printHelpMenu();
break;
case 'v'://提示 trace info
*isVerbose=1;
break;
case 's'://读取s
checkOptarg(optarg);
*s = atoi(optarg);//将字符拆转化位整型
break;
case 'E'://读取E
checkOptarg(optarg);
*E = atoi(optarg);
break;
case 'b'://读取b
checkOptarg(optarg);
*b = atoi(optarg);
break;
case 't'://读取t
checkOptarg(optarg);
strcpy(tracefileName,optarg);//C中char*不能直接赋值
break;
default://输入错误时,输出help信息
printHelpMenu();
exit(0);//退出程序
break;
}
}
return 1;
}
编写读取trace文件并分支处理LSM命令函数
在cache执行的时候会从trace文件中读取指令,这些指令的格式是固定的
操作 | 属性 | 产生影响 | 备注 |
---|---|---|---|
I | 指令加载 | 指令加载标识 I前无空格 | |
L | 数据加载,从cache中指定位置读取数据 | 加载可能会有命中hit,失效miss,或当一组已满,发生驱逐eviction | L前有空格 |
S | 数据存储,将数据写到cache中的指定地址 | 存储可能会有命中hit,失效miss,或当一组已满,发生驱逐eviction | S前有空格 |
M | 数据修改,一次连续读和写操作a load followed by a store to the same address. | 会有连续的读操作和写操作。如果第一次load未命中,将会有miss,继续store可能会有驱逐eviction,最后一次hit。也有可能不发生驱逐,这是是miss hit当然第一次访问也可能hit,所以也可能有两次hit | M前有空格 |
//while(scanf("%c %x,%d",&identifier,&address,&size)!=EOF){
while(fscanf(pFile,"%c %x,%d",&identifier,&address,&size)!=EOF){
int setBits=getSet(address,s,b);
int tagBit=getTag(address,s,b);
int block=getBlock(address,b);
switch (identifier)
{
case 'I':
break;
case 'L':if(isVerbose) dealCommand(identifier,address,size,setBits,tagBit,block);
loadData(cache,E,setBits,tagBit,isVerbose);
break;
case 'M':if(isVerbose) dealCommand(identifier,address,size,setBits,tagBit,block);
mmodifyData(cache,E,setBits,tagBit,isVerbose);
break;
case 'S': dealCommand(identifier,address,size,setBits,tagBit,block);
storeData(cache,E,setBits,tagBit,isVerbose);
break;
default:break;
}
}
LRU算法实现函数
当整个组都被占用时发生容量失效,这个时候将会用LRU替换规则驱逐最近最少访问的行。具体的LRU算法实现的过程是:
- 如果某一组的某一行发生了hit,那么这一行的lru值置为maxn
- 这一组所有的行的lru-1
- 如果发生驱逐将会替换掉最近最少访问的行,即lru最小
void updateLruNumber(Sim_Cache* cache,int setBits,int index)//whice cache line to evict
{
int i;
for(i=0;i<cache->line_num;i++)
{
cache->sets[setBits].line[i].lruNumber--;
}
cache->sets[setBits].line[index].lruNumber=Maxn;
}
编写查找某组中牺牲行的函数代码
在某一组中查找lru值最小的一行,返回该行的行号index
int findMinLruNumber(Sim_Cache* cache,int setBits)
{
int i=0,minLRU=0x7fffffff,index;
for(i=0;i<cache->line_num;i++)
{//找到最近最少被使用的字段
if(cache->sets[setBits].line[i].lruNumber<minLRU)
{
minLRU=cache->sets[setBits].line[i].lruNumber;
index=i;
}
}//牺牲index
return index;
}
编写命中判断的函数代码
- 遍历该组,寻找与tag匹配的行
- 如果匹配则刷新该行lru并返回0
- 否则返回0
int isMiss(Sim_Cache* cache,int setBits,int tagBit)
{
int i;
for(i=0;i<cache->line_num;i++)
{
if(cache->sets[setBits].line[i].valid&&(cache->sets[setBits].line[i].tag==tagBit)) //命中
{
updateLruNumber(cache,setBits,i);//刷新该行
return 0;//返回命中
}
}
return 1;//返回未命中
}
编写更新高速缓存cache的函数代码
① 判断组是否满来分为牺牲行或不牺牲行。组的每行的有效位全为1表示全满。
② 更新时加载新的数据块要更新有效位为1,更新标记位,更新Lru计数值。
③ 返回值为evic,用于判断是否冲突
int updateCache(Sim_Cache* cache,int setBits,int tagBit)
{
int i,index=-1,evic=0;
for(i=0;i<cache->line_num;i++)
{
if(!cache->sets[setBits].line[i].valid)//找到未满的行更新
{
index=i;
break;
}
}
//不存在未满的行
if(index==-1)
{
index=findMinLruNumber(cache,setBits);//找到最小的lru所在行
evic=1;
}
cache->sets[setBits].line[index].valid=1;
cache->sets[setBits].line[index].tag=tagBit;
updateLruNumber(cache,setBits,index);
return evic;//返回是否冲突
}
编写加载数据的L命令处理函数代
① getopt详细模式-v时,verbose=1,据此判断是否打印出相应结果miss,hit,eviction.
② 若命中,则miss++返回,否则miss++,接着判断是否冲突,是则eviction++
void loadData(Sim_Cache*cache,int E,int setBits,int tagBit,int verbose)
{
if(!isMiss(cache,setBits,tagBit))//命中
{
hit++;
if(verbose) printf(" hit");
//LruDisplay(cache);
return;
}
miss++;//未命中
if(verbose) printf(" miss");
//处理写入,可能会出现冲突
if(updateCache(cache,setBits,tagBit))//冲突,发生驱逐
{
if(verbose) printf(" eviction");
eviction++;
//LruDisplay(cache);
}
//LruDisplay(cache);
}
编写存储数据的S命令处理函数和修改数据的M命令处理函数
- store直接调用load函数
- Modify是对地址的两次连续访问
void storeData(Sim_Cache* cache,int E,int setBits,int tagBit,int verbose)
{//store直接调用load函数
loadData(cache,E,setBits,tagBit,verbose);
}
void mmodifyData(Sim_Cache* cache,int E,int setBits,int tagBit,int verbose)
{//Modify是对地址的两次连续访问
loadData(cache,E,setBits,tagBit,verbose);
storeData(cache,E,setBits,tagBit,verbose);
}
编写获取trace脚本操作地址中的组索引与标记位的函数
① 组索引获取函数getSet:将address逻辑右移b位用于移除block,然后将0xffffffff逻辑右移32-s位,最后二者位于,取出address
② 标记位获取函数getTag:将address逻辑右移s+b位,从而去掉set和block,得到tag位
int getSet(int address,int s,int b)
{
return (address>>b)&(0xffffffff>>(32-s));
}
int getTag(int address,int s,int b)
{
return (address>>(s+b));
}
int getBlock(int address,int b)
{
unsigned a=address;
return (a<<(32-b))>>(32-b);
}
执行结果
总代码实现
#include "cachelab.h"
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
const int Maxn=0x7fffffff-1;
int hit=0,miss=0,eviction=0;//要模拟的三个变量,命中,失效,冲突
//定义cache的结构
typedef struct{
int valid;//有效位
int tag;//标识位
int lruNumber;//LRU算法计数值
}Line;//cache line行
typedef struct{
Line *line;//一组中的所有行
}Set;//cache set 组
typedef struct{
int set_num;//组数
int line_num;//每组的行数
Set *sets;//整个cache的多个组
}Sim_Cache;
void putSets(Sim_Cache* cache)
{
printf("S=%d,E=%d\n",cache->set_num,cache->line_num);
int i,j;
for(i=0;i<cache->set_num;i++)
{
printf("set%d: ",i);
for(j=0;j<cache->line_num;j++)
{
//printf("Line%d ",j+1);
printf("<%d,%d,%x> ",cache->sets[i].line[j].valid,cache->sets[i].line[j].tag,cache->sets[i].line[j].lruNumber);
}
printf("\n");
}
}
void init_SimCache(int s,int E,int b,Sim_Cache *cache)//按照手册提示,初始化
{
int S=(1<<s);//2^s
cache->set_num=S;//初始化参数S
cache->line_num=E;//初始化参数E
cache->sets=(Set*)malloc(sizeof(Set)*S);//开辟组
int i,j;
for(i=0;i<S;i++)//开辟S行line
cache->sets[i].line=(Line*)malloc(sizeof(Line)*E);
for(i=0;i<S;i++)//开辟Line
{//初始化有效位,标识位,LRU位
for(j=0;j<E;j++)
{
cache->sets[i].line[j].valid=0;//有效位
cache->sets[i].line[j].tag=0;//标识位
cache->sets[i].line[j].lruNumber=0;//LRU位
}
}
// putSets(cache);
}
void free_SimCache(Sim_Cache *cache)
{
int i;
for(i=0;i<cache->set_num;i++) free(cache->sets[i].line);//释放行
free(cache->sets);//释放组
cache->line_num=0;
cache->set_num=0;
//printf("free the cache succeful!\n");
}
void updateLruNumber(Sim_Cache* cache,int setBits,int index)//whice cache line to evict
{
int i;
for(i=0;i<cache->line_num;i++)
{
cache->sets[setBits].line[i].lruNumber--;
}
cache->sets[setBits].line[index].lruNumber=Maxn;
}
int findMinLruNumber(Sim_Cache* cache,int setBits)
{
int i=0,minLRU=0x7fffffff,index;
for(i=0;i<cache->line_num;i++)
{//找到最近最少被使用的字段
if(cache->sets[setBits].line[i].lruNumber<minLRU)
{
minLRU=cache->sets[setBits].line[i].lruNumber;
index=i;
}
}//牺牲index
return index;
}
int isMiss(Sim_Cache* cache,int setBits,int tagBit)
{
int i;
for(i=0;i<cache->line_num;i++)
{
if(cache->sets[setBits].line[i].valid&&(cache->sets[setBits].line[i].tag==tagBit)) //命中
{
updateLruNumber(cache,setBits,i);//刷新该行
return 0;//返回命中
}
}
return 1;//返回未命中
}
int updateCache(Sim_Cache* cache,int setBits,int tagBit)
{
int i,index=-1,evic=0;
for(i=0;i<cache->line_num;i++)
{
if(!cache->sets[setBits].line[i].valid)//找到未满的行更新
{
index=i;
break;
}
}
//不存在未满的行
if(index==-1)
{
index=findMinLruNumber(cache,setBits);//找到最小的lru所在行
evic=1;
}
cache->sets[setBits].line[index].valid=1;
cache->sets[setBits].line[index].tag=tagBit;
updateLruNumber(cache,setBits,index);
return evic;//返回是否冲突
}
void LruDisplay(Sim_Cache* cache)
{
int i,j;
printf("-display LRU info:---------------------\n");
for(i=0;i<cache->set_num;i++)
{
printf("set%d: ",i);
for(j=0;j<cache->line_num;j++)
{
printf("<%d,%d,%x> ",cache->sets[i].line[j].valid,cache->sets[i].line[j].tag,cache->sets[i].line[j].lruNumber);
}
printf("\n");
}
}
void loadData(Sim_Cache*cache,int E,int setBits,int tagBit,int verbose)
{
if(!isMiss(cache,setBits,tagBit))//命中
{
hit++;
if(verbose) printf(" hit");
//LruDisplay(cache);
return;
}
miss++;//未命中
if(verbose) printf(" miss");
//处理写入,可能会出现冲突
if(updateCache(cache,setBits,tagBit))//冲突,发生驱逐
{
if(verbose) printf(" eviction");
eviction++;
//LruDisplay(cache);
}
//LruDisplay(cache);
}
void storeData(Sim_Cache* cache,int E,int setBits,int tagBit,int verbose)
{//store直接调用load函数
loadData(cache,E,setBits,tagBit,verbose);
}
void mmodifyData(Sim_Cache* cache,int E,int setBits,int tagBit,int verbose)
{//Modify是对地址的两次连续访问
loadData(cache,E,setBits,tagBit,verbose);
storeData(cache,E,setBits,tagBit,verbose);
}
void printHelpMenu()
{
printf("The reference simulator takes the following command-line arguments:\n\
Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>\n\
-h: Optional help flag that prints usage info\n\
-v: Optional verbose flag that displays trace info\n\
-s <s>: Number of set index bits (S = 2^s is the number of sets)\n\
-E <E>: Associativity (number of lines per set)\n\
-b <b>: Number of block bits (B = 2^b is the block size)\n\
-t <tracefile>: Name of the valgrind trace to replay\n");
}
void checkOptarg(char* curOptarg)
{
if(curOptarg[0]=='-')//如果参数curOptarg='-'说明提取错误,退出程序
{
printHelpMenu();//打印帮助信息
exit(0);
}
}
int get_opt(int argc,char *argv[],int *s,int *E,int* b,char *tracefileName,int *isVerbose)
{
int opt;
while((opt=getopt(argc,argv,"hvs:E:b:t:"))!=-1){
switch (opt) {
case 'h'://打印帮助信息
printHelpMenu();
break;
case 'v'://提示 trace info
*isVerbose=1;
break;
case 's'://读取s
checkOptarg(optarg);
*s = atoi(optarg);//将字符拆转化位整型
break;
case 'E'://读取E
checkOptarg(optarg);
*E = atoi(optarg);
break;
case 'b'://读取b
checkOptarg(optarg);
*b = atoi(optarg);
break;
case 't'://读取t
checkOptarg(optarg);
strcpy(tracefileName,optarg);//C中char*不能直接赋值
break;
default://输入错误时,输出help信息
printHelpMenu();
exit(0);//退出程序
break;
}
}
return 1;
}
int getSet(int address,int s,int b)
{
return (address>>b)&(0xffffffff>>(32-s));
}
int getTag(int address,int s,int b)
{
return (address>>(s+b));
}
int getBlock(int address,int b)
{
unsigned a=address;
return (a<<(32-b))>>(32-b);
}
void dealCommand(int identifier,int address,int size,int setBits,int tagBit,int block)
{
printf("\n %c %08x,%d",identifier,address,size);
printf(" | set %-8d tag %-8d block %-8d",setBits,tagBit,block);
}
int main(int argc,char *argv[])
{
Sim_Cache* cache=(Sim_Cache*)malloc(sizeof(Sim_Cache));//cach
char *tracefileName=(char*)malloc(sizeof(char));//文件路径
int s=0,E=0,b=0,isVerbose=0;//集合位,行位,块位
get_opt(argc,argv,&s,&E,&b,tracefileName,&isVerbose);
init_SimCache(s,E,b,cache);//初始化
FILE* pFile=fopen(tracefileName,"r");//以只读的方式打开fileTrace
if(!pFile)
{
printf("fail to open traceFiles!\n");//打开失败
return 0;
}
char identifier=' ';//操作命令符
int address=0;//读取地址
int size=0;//块偏移
printf("s%d e%d b%d\n",s,E,b);
//while(scanf("%c %x,%d",&identifier,&address,&size)!=EOF){
while(fscanf(pFile,"%c %x,%d",&identifier,&address,&size)!=EOF){
int setBits=getSet(address,s,b);
int tagBit=getTag(address,s,b);
int block=getBlock(address,b);
switch (identifier)
{
case 'I':
break;
case 'L':if(isVerbose) dealCommand(identifier,address,size,setBits,tagBit,block);
loadData(cache,E,setBits,tagBit,isVerbose);
break;
case 'M':if(isVerbose) dealCommand(identifier,address,size,setBits,tagBit,block);
mmodifyData(cache,E,setBits,tagBit,isVerbose);
break;
case 'S': dealCommand(identifier,address,size,setBits,tagBit,block);
storeData(cache,E,setBits,tagBit,isVerbose);
break;
default:break;
}
}
fclose(pFile);//关闭文件
printf("\n");
free_SimCache(cache);
//printf("Summary: ");
printSummary(hit,miss,eviction);//打印模拟结果
return 0;
}