深入理解计算机系统CSAPP: Cachelab使用LRU策略的模拟缓存命中
Writing a Cache Simulator任务简介
使用LRU策略,编写csim.c,模拟cache的运行。cache为多路组相联,组数、每组行数、每行块数由调用参数给出,具体看WriteUp。材料中给出了一个名为csim-ref的可执行文件,要求我们程序最后执行的结果和它一样。
valgrind分析缓存操作
valgrind --log-fd=1 --tool=lackey -v --trace-mem=yes ls -l
该指令会跟踪地址访问情况
L 10,1
M 20,1
L 22,1
S 18,1
L 110,1
L 210,1
M 12,1
L 数据读取 M读取后写入 S数据写入
csim-ref需要达到的效果如下:
csim-ref的指令
./csim-ref -s 4 -E 1 -b 4 -t traces/yi.trace
./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace
运行效果:
./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace
L 10,1 miss
M 20,1 miss hit
L 22,1 hit
S 18,1 hit
L 110,1 miss eviction
L 210,1 miss eviction
M 12,1 miss eviction hit
hits:4 misses:5 evictions:3
注意地址使用16进制表示的
提示: 使用getopt来解析指令:
./csim -v -s 4 -E 1 -b 4 -t traces/yi.trace
不用着急确定tag位的长度和地址位数,不影响操作。
b=4 ,即低4位是偏移位。s=4 组索引位是中间4位。
为了提取索引位和tag位,使用DataLab中的操作方法:
unsigned mask = 0xffffffff;
unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
unsigned index_set = (address >> b) & mask_set;
unsigned curtag = address >> (b + s);
解题思路
大的思路是,模拟一个LRU,建立一个SET的数组sets(模拟了连续的多个cache set),每个set中记录一个双向链表,该链表中每个Node表示一个cache line,链表的最大长度为E,超过E时从尾部删除节点,cache miss时从头结点插入,每次访问一个数据hit时先删除后把其加入到头结点。
数据结构构造
其数据结构如下
typedef struct _Node
{
unsigned tag;
struct _Node* next;
struct _Node* pre; //struct _Node这里必须使用这种方式,因为编译器还不认识Node
}Node; //每个Node表示一个行,行里面可以有多个block,但是本程序不关心block的具体操作。切不关心size超出当前block的情况
typedef struct _SET
{
Node* head; //记录头,但是头节点不存放东西,
Node* tail; //用于快捷访问set中的尾行,同样,本节点也不放东西
int* size_E;
/*
试试直接分配, 好像不大行必须让里面全是。 这个原因目前还没找到,
为什么呢。不能字节设置成int,因为在函数里面传送的SET的地址,
是一个SET的复制体,只有通过对这个复制体中的地址进行操作才能修改这个值
*/
}SET; //每个set记录当前set的头尾节点,便于做头插和尾删操作
static SET* sets; //创建一个set的数组,因为不知道开多大,创建一个指针之后malloc分配
对应的,我们需要sets的初始化,双向链表的尾删,头插等函数。
void initializeSET(int i){
// SET curset = sets[i]; //这样好像不大行,不能这样赋值
//因为这样的话,set是重新创建的一个set,未对原来的set做修改
// curset.head = malloc(sizeof(Node));
// curset.tail = malloc(sizeof(Node));
// curset.head->next = curset.tail;
// curset.tail->pre = curset.head;
// curset.size_E = malloc(sizeof(int));
// *(curset.size_E) = 0; //初试时刻尚未被占用
sets[i].head = malloc(sizeof(Node));
sets[i].tail = malloc(sizeof(Node));
sets[i].head->next = sets[i].tail;
sets[i].tail->pre = sets[i].head;
(sets[i].size_E) = (int* )malloc(sizeof(int));
*(sets[i].size_E) = 0; //初试时刻尚未被占用
}
void remove_node(SET set, Node* node){
node->next->pre = node->pre;
node->pre->next = node->next;
free(node);
*(set.size_E) = *(set.size_E) - 1;
}
void add_head(SET set, int tag){
Node* node = malloc(sizeof(Node));
node->tag = tag;
node->pre = set.head;
node->next = set.head->next;
//还未完成连接
set.head->next->pre = node;
set.head->next = node;
*(set.size_E) = *(set.size_E) +1;
}
参数读入与解析getopt
为了解析输入参数,我们使用getopt函数
void phase_opt(int argc, char * const argv[], char *filename){
int option;
while((option = getopt(argc, argv, "s:E:b:t:")) !=-1){
//opt会被转换成char到整数
switch(option){
case 's':
s = atoi(optarg);
break;
case 'E':
E = atoi(optarg);
break;
case 'b':
b = atoi(optarg);
break;
case 't':
strcpy(filename,optarg);
}
}
Total_set = 1<<s; //总组数
}
分行解析cache_simulation
对每一行数据,我们要读取一行后进行解析
void cache_simulation(char* filename){
//初始化sets
sets = (SET*)malloc(Total_set*sizeof(SET)); //内容在堆上
//对sets内的每个元素进行初始化
for(int i = 0; i < Total_set; i++)initializeSET(i);
FILE* file = fopen(filename, "r");
char op;
unsigned address;
int size;
//地址默认是十六进制输入的
while(fscanf(file, " %c %x,%d", &op, &address, &size) > 0){
printf("\n%c %x, %d", op, address, size);
switch (op)
{
case 'L':
update(address, size);
break;
case 'M':
update(address, size);
//注意这里很巧妙地没有break,因为M需要update两次
case 'S':
update(address, size);
break;
}
}
}
利用了switch,对M很巧妙地没有break,因为M需要update两次
模拟对一个地址的访问update
void update(unsigned address, int size){
//需要通过一个链表来实现 模拟LRU 刚用过的就放在链表头部,需要踢除的话从链表尾部踢除
//现在想办法获得address的tag位和set位
unsigned mask = 0xffffffff;
unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
unsigned index_set = (address >> b) & mask_set;
unsigned curtag = address >> (b + s);
//完成了索引提取,之后需要进行模拟了
SET curset = sets[index_set];
Node* curNode = curset.head->next; //这里引发了段错误
//开始从头结点之后遍历
while(curNode != curset.tail){
if (curNode->tag == curtag)
{
hit++;
printf(" hit");
//找到节点后需要把当前节点放到最前面;可以通过先删除后增加的方法
remove_node(curset, curNode);
add_head(curset, curtag);
break;
}
curNode = curNode->next;
}
if(curNode == curset.tail){
//未找到
misses++;
printf(" misses");
if(*(curset.size_E) == E){
evictions++;
printf(" evictions");
remove_node(curset, curset.tail->pre);
add_head(curset, curtag);
}
else {
add_head(curset, curtag);
}
}
}
完整代码如下
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cachelab.h"
#define addrlen 8
static int s, E, b, Total_set; //静态全局变量,仅仅在当前函数中可用的全局变量
static int hit, misses, evictions;
typedef struct _Node
{
unsigned tag;
struct _Node* next;
struct _Node* pre; //struct _Node这里必须使用这种方式,因为编译器还不认识Node
}Node; //每个Node表示一个行,行里面可以有多个block,但是本程序不关心block的具体操作。切不关心size超出当前block的情况
typedef struct _SET
{
Node* head; //记录头,但是头节点不存放东西,
Node* tail; //用于快捷访问set中的尾行,同样,本节点也不放东西
int* size_E;
/*
试试直接分配, 好像不大行必须让里面全是。 这个原因目前还没找到,
为什么呢。不能字节设置成int,因为在函数里面传送的SET的地址,
是一个SET的复制体,只有通过对这个复制体中的地址进行操作才能修改这个值
*/
}SET; //每个set记录当前set的头尾节点,便于做头插和尾删操作
static SET* sets; //创建一个set的数组,因为不知道开多大,创建一个指针之后malloc分配
void phase_opt(int argc, char * const argv[], char *filename);
void cache_simulation(char* filename);
void update(unsigned address, int size);
void initializeSET(int i);
void remove_node(SET set, Node* node);
void add_head(SET set, int tag);
int main(int argc, char *argv[])
{
char *filename = malloc(100*sizeof(char));
//存放filename,这里其实不用传**也可以
phase_opt(argc, argv, filename);
//printf("%d, %d, %d, %s", s, E, b, filename);
cache_simulation(filename);
printf("\n");
printSummary(hit, misses, evictions);
return 0;
}
void phase_opt(int argc, char * const argv[], char *filename){
int option;
while((option = getopt(argc, argv, "s:E:b:t:")) !=-1){
//opt会被转换成char到整数
switch(option){
case 's':
s = atoi(optarg);
break;
case 'E':
E = atoi(optarg);
break;
case 'b':
b = atoi(optarg);
break;
case 't':
strcpy(filename,optarg);
}
}
Total_set = 1<<s; //总组数
}
void update(unsigned address, int size){
//需要通过一个链表来实现 模拟LRU 刚用过的就放在链表头部,需要踢除的话从链表尾部踢除
//现在想办法获得address的tag位和set位
unsigned mask = 0xffffffff;
unsigned mask_set = mask >> (32-s); //只有s个1的maks 这里用到了Lab1 datalab的基础知识
unsigned index_set = (address >> b) & mask_set;
unsigned curtag = address >> (b + s);
//完成了索引提取,之后需要进行模拟了
SET curset = sets[index_set];
Node* curNode = curset.head->next; //这里引发了段错误
//开始从头结点之后遍历
while(curNode != curset.tail){
if (curNode->tag == curtag)
{
hit++;
printf(" hit");
//找到节点后需要把当前节点放到最前面;可以通过先删除后增加的方法
remove_node(curset, curNode);
add_head(curset, curtag);
break;
}
curNode = curNode->next;
}
if(curNode == curset.tail){
//未找到
misses++;
printf(" misses");
if(*(curset.size_E) == E){
evictions++;
printf(" evictions");
remove_node(curset, curset.tail->pre);
add_head(curset, curtag);
}
else {
add_head(curset, curtag);
}
}
}
void cache_simulation(char* filename){
//初始化sets
sets = (SET*)malloc(Total_set*sizeof(SET)); //内容在堆上
//对sets内的每个元素进行初始化
for(int i = 0; i < Total_set; i++)initializeSET(i);
FILE* file = fopen(filename, "r");
char op;
unsigned address;
int size;
//地址默认是十六进制输入的
while(fscanf(file, " %c %x,%d", &op, &address, &size) > 0){
printf("\n%c %x, %d", op, address, size);
switch (op)
{
case 'L':
update(address, size);
break;
case 'M':
update(address, size);
//注意这里很巧妙地没有break,因为M需要update两次
case 'S':
update(address, size);
break;
}
}
}
void initializeSET(int i){
// SET curset = sets[i]; //这样好像不大行,不能这样赋值
//因为这样的话,set是重新创建的一个set,未对原来的set做修改
// curset.head = malloc(sizeof(Node));
// curset.tail = malloc(sizeof(Node));
// curset.head->next = curset.tail;
// curset.tail->pre = curset.head;
// curset.size_E = malloc(sizeof(int));
// *(curset.size_E) = 0; //初试时刻尚未被占用
sets[i].head = malloc(sizeof(Node));
sets[i].tail = malloc(sizeof(Node));
sets[i].head->next = sets[i].tail;
sets[i].tail->pre = sets[i].head;
(sets[i].size_E) = (int* )malloc(sizeof(int));
*(sets[i].size_E) = 0; //初试时刻尚未被占用
}
void remove_node(SET set, Node* node){
node->next->pre = node->pre;
node->pre->next = node->next;
free(node);
*(set.size_E) = *(set.size_E) - 1;
}
void add_head(SET set, int tag){
Node* node = malloc(sizeof(Node));
node->tag = tag;
node->pre = set.head;
node->next = set.head->next;
//还未完成连接
set.head->next->pre = node;
set.head->next = node;
*(set.size_E) = *(set.size_E) +1;
}