CSAPP Cachelab使用LRU策略的模擬快取命中
阿新 • • 發佈:2022-03-17
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;
}