1. 程式人生 > 其它 >CSAPP Cachelab使用LRU策略的模擬快取命中

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

參考資料

官方Writup