# 一、Cache 模拟器实验
## 1.1 实验目的
- 理解 cache 工作原理;
- 如何实现一个高效的模拟器
## 1.2 实验环境
Linux 64-bit ,C 语言
## 1.3 实验思路
### 1.3.1 cache 模拟
建立 cache 的数据结构,包括有效位 valid、标签位 tag 和使用记录器 lru。实现代码如下:
```c++
typedef struct cache_line {
char valid;
mem_addr_t tag;
unsigned long long int lru;
} cache_line_t;
```
定义 cache 组:用数组模拟 cache 组,即建立关于 cache 组的一个指针,然后动态开辟所需要 cache 的数量。实现代码如下:
```c++
typedef cache_line_t* cache_set_t;
```
定义 cache:用 cache_set 数组模拟 cache,即一个 cache 包含多个 cache 组,同样先创建一个 cache 指针,然后根据所需要 cache 组的大小动态开辟 cache 的实际大小。实现代码如下:
```c++
typedef cache_set_t* cache_t;
```
### 1.3.2 输入输出参数
- verbosity:显示轨迹信息,输入指令中含-v 则 verbosity 置 1,表示要进行轨迹信息输出,否则 verbosity 置 0。
- s:组索引位数,输入指令中-s 后面的数据为组索引位数,用 S=2s 表示所需要的 cache 组数。
- b:内存块内地址位数,输入指令-b 后面的数据为块内存地址组数,用 B=2b 表示所需要划分 cache 的内存块大小。
- E:关联度(每组包含的缓存行数),输入指令后面的数据为关联度,即每一组有多少个块。
- miss_count:用于记录未命中的次数。
- hit_count:用于记录命中的次数。
- eviction_count:用于记录淘汰的次数。
### 1.3.3 函数 initCache()
功能:初始化 cache。
实现方法:对于给定的参数 S、B、E 利用 malloc 函数动态开辟 cache 所需要的内存大小。实现代码如下:
```c++
void initCache() {
int i,j;
cache = (cache_set_t*) malloc(sizeof(cache_set_t) * S);
for (i=0; i<S; i++) {
cache[i]=(cache_line_t*) malloc(sizeof(cache_line_t) * E);
for (j=0; j<E; j++) {
cache[i][j].valid = 0;
cache[i][j].tag = 0;
cache[i][j].lru = 0;
}
}
set_index_mask = (mem_addr_t) (pow(2, s) - 1);
}
```
### 1.3.4 函数 freeCache()
功能:释放 cache 所占用的内存。
实现方法:用循环遍历 cache 所申请的指针数组内存块,以此 free 掉各个内存块。实现代码如下:
```c++
void freeCache() {
int i;
for(i=0; i<S; i++) {
free(cache[i]);
}
free(cache);
}
```
### 1.3.5 函数 accessData()
功能:模拟 cache 进行的访问,给出所访问内存地址对应的是否命中或者是否淘汰。
实现方法:将输入地址和 set_index_mask 求与后得到一个索引,然后输入地址的前 s+b 位作为标签。依次查询每个块,并对比所需要的索引和标签,如果找到了相应数据,则 hit_count 加 1,否则 miss_count 加 1。并且在未命中时,先查询块中是否有空位置,有的话直接占用此空位置,否则需要找到 lru 最小的行(即最近访问时间间隔最长的行),然后进行数据的替换,eviction_count 加 1。实现代码如下:
```c++
void accessData(mem_addr_t addr) {
int i;
unsigned long long int eviction_lru = ULONG_MAX;
unsigned int eviction_line = 0;
int haveEmpty=0;//是否含有空位置
mem_addr_t set_index = (addr >> b) & set_index_mask;
mem_addr_t tag = addr >> (s+b);
int hit=0;
cache_set_t cache_set = cache[set_index];
for(i=0; i<E; i++) //E:相联度 即每一个组内有几块{
//所查询的块在cache内
if(cache_set[i].tag==tag&&cache_set[i].valid==1) {
if(verbosity) printf("hit ");
hit_count++;
cache_set[i].lru = ULONG_MAX;
hit = 1;
}
else if(!haveEmpty&&cache_set[i].valid==0) {
haveEmpty=1;
eviction_line=i;
}
else if(cache_set[i].valid==1) {
//每进行一次查询,含有数据的块lru减1
cache_set[i].lru--;
//找到lru最小的位置,作为预备淘汰
if(cache_set[i].lru<eviction_lru) {
eviction_lru=cache_set[i].lru;
eviction_line=i;
}
}
}
//未命中
if(hit==0) {
if(verbosity) printf("miss ");
miss_count++;
if(cache_set[eviction_line].valid==1)//有淘汰{
if(verbosity) printf("eviction ");
eviction_count ++;
}
cache_set[eviction_line].valid=1;
cache_set[eviction_line].lru=ULONG_MAX;
cache_set[eviction_line].tag=tag;
}
}
```
### 1.3.6 函数 replayTrace
功能:读取 trace 轨迹文件的内容,并根据其指令进行模拟内存访问的过程。
实现方法:通过文件读取函数 fscanf 进行文件流出读取,并将读取的内容赋值到 operation、addr 和 len 变量中,分别代表操作类型、地址、长度。然后根据 operation 的类型进行相应的函数调用。I 指令忽略,S 和 L 指令进行一次 cache 查询,即调用一次 accessData 函数,M 指令进行两次 cache 查询。实现代码如下:
```c++
void replayTrace(char* trace_fn) {
mem_addr_t addr=0;
unsigned int len=0;
char operation;
FILE* trace_fp = fopen(trace_fn, "r");
while(fscanf(trace_fp,"%c %llx,%d",&operation,&addr,&len)!=EOF) {
if(operation=='I'&&verbosity)
printf("%c %llx,%u ",operation,addr,len);
else if(operation=='L'||operation=='S'||operation=='M') {
if(verbosity) printf("%c %llx,%u ",operation,addr,len);
accessData(addr);
if(operation=='M') accessData(addr);
if(verbosity) printf("\n");
}
}
fclose(trace_fp);
}
```
### 1.3.7 函数 printUsage()
功能:当有输入格式错误或者输入-h 指令时,输出相应的信息。
实现方法:用 printf 函数输出相关信息。实现代码如下:
```c++
void printUsage(char* argv[]) {
printf("Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n", argv[0]);
printf("Options:\n");
printf(" -h Print this help message.\n");
printf(" -v Optional verbose flag.\n");
printf(" -s <num> Number of set index bits.\n");
printf(" -E <num> Number of lines per set.\n");
printf(" -b <num> Number of block offset bits.\n");
printf(" -t <file> Trace file.\n");
printf("\nExamples:\n");
printf(" linux> %s -s 4 -E 1 -b 4 -t traces/yi.trace\n", argv[0]);
printf(" linux> %s -v -s 8 -E 2 -b 4 -t traces/yi.trace\n", argv[0]);
exit(0);
}
```
## 1.4 实验结果和分析
修改完成 csim.c 文件后,进行 make 编译,并运行 test-csim,得到的测试结果如下:
![1](./photo/1-325a3ade69f5b7c7ff97958fa0dee3d4.png)
由运行结果可知,cache 模拟正确。
# 二、总结和体会
对 cache 进行的实验模拟算是比较简单的实验,仅仅只是模拟一个建议的 cache 然后对指令访问的命中和淘汰进行记录。需要考虑的函数实现大概就是 accessData 和 replayTrace,前者要进行 cache 内数据的查找和替换,后者是对文件的读取和指令类型判断。总体上来说,完成 cache 模拟实验加深了对 cache 运行模式的认知,也很好的理解了 LRU 淘汰策略的工作机制。
# 三、对实验课程的建议
所给的实验说明比较少,初步读完后并不知道需要如何开始实验,建议多给一些相关的提示,如需要实现的函数构建、cache 设计的基本思路等。
实验总体难度偏低,可以加大实验难度。
# 四、使用说明
1. 实验环境为Linux-64位
2. 实验所需要设计的文件位csim.c,即设计cache并进行模拟仿真
3. 所有的make文件均已经编写好,完成csim.c的编写后,在命令行输入如下命令进行编译
```
$ make clean
$ make
```
4. 编译完后会得到csim输出文件,输入对应的指令即可以模拟cache运行�
神仙别闹
- 粉丝: 3819
- 资源: 7471
最新资源
- bdwptqmxgj11.zip
- onnxruntime-win-x86
- onnxruntime-win-x64-gpu-1.20.1.zip
- vs2019 c++20 语法规范 头文件 <ratio> 的源码阅读与注释,处理分数的存储,加减乘除,以及大小比较等运算
- 首次尝试使用 Win,DirectX C++ 中的形状渲染套件.zip
- 预乘混合模式是一种用途广泛的三合一混合模式 它已经存在很长时间了,但似乎每隔几年就会被重新发现 该项目包括使用预乘 alpha 的描述,示例和工具 .zip
- 项目描述 DirectX 引擎支持版本 9、10、11 库 Microsoft SDK 功能相机视图、照明、加载网格、动画、蒙皮、层次结构界面、动画控制器、网格容器、碰撞系统 .zip
- 项目 wiki 文档中使用的代码教程的源代码库.zip
- 面向对象的通用GUI框架.zip
- 基于Java语言的PlayerBase游戏角色设计源码
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈