CSAPP的CacheLab

这个是做CacheLab的记录,part A的内容是做一个和csim-ref一样功能的cache模拟器,使用LRU替换策略。

有一说一第一次看到这个lab的时候还是吓一跳的,因为题目说是修改csim.c文件即可,我一开始以为是文件里框架什么的都给好了只需要适当填空就行,没想到打开后发现只有一句printSummary()函数。
不过其实慢慢做下来发现也没有想象中复杂,最近寒假每天抽出来四五个小时,三天就写完了part A。

但是part B的blocking技术那块现在理解的还不是很好,而且也不知道如何处理任意行列的矩阵转置的优化这个问题。因此也就还没有做,以后有时间再回头写写吧。

cache结构

不论是全相联、组相联还是直接映射,cache都可以用一个四元组(S, E, B, m)来表示,其中一个cache中有S = 2s个组,每组有E行,每行(块)的大小是B = 2b字节。一个地址有m位,其格式为:

t位s位b位
标记CT组索引CI块偏移CO

LRU替换策略

LRU的实现是这样的:对每个行设置一个LRU标记位LRU_tag。每次访问cache时将新加入或命中的行的LRU_tag置为0,并将其他行的LRU_tag加一。当需要替换时,将LRU_tag最大的行换出。

相对应的,LFU主要是从次数来考虑,而LRU主要是从“上次访问的时间”来考虑。

命令行参数getopt()

首先要考虑的是命令行参数,这个可以使用在<unistd.h>中的getopt()函数来实现。具体使用方法网上都可以找到。
需要注意的是我在实现的时候会报错说getopt()函数未声明,查阅后只需添加头文件<getopt.h>即可。

字符串分割strtok()

trace文件里读取address,size格式的数据,需要用到字符串分割。本来在网上搜到说以前使用的strtok()是线程不安全的,后来都要使用strsep()。但我在实现的时候使用strsep()会报错,查找后知道这并不是一个标准里的函数,有些地方可能没有支持,因此最终还是使用了strtok来分割字符串。

访问cache

无论是S、L还是M指令,它们对cache的访问行为都是相同的,即检查cache中是否有需要的地址。只有M特殊在它相当于是L+S,因此它的第二次访问是一定会命中的。
因此可以把三个指令抽象为一个相同的访问cache的行为。

代码

下面是我的实现代码。

#include "cachelab.h"
#include <unistd.h>
#include <getopt.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void print_help(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("Examples:\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]);
}

/*
 * cache的结构
 * 每个cache由若干组组成,每个组由若干行组成
 * 因此先定义cache行,再定义cache组,最后组成cache
 */
// 写这个的时候才知道C语言不用typedef而只使用struct会出错
typedef struct {
    int is_valid;
    int LRU_tag;
    unsigned long tag;  // 使用unsigned long而非long,原因在主函数中提及
    char *blocks;   // char等价于byte,块大小取决于参数b
}Cache_line;
typedef struct {
    Cache_line *lines;	// 行数量取决于参数E
}Cache_set;
typedef struct {
	Cache_set *sets;    // 组数量取决于参数s
}Cache;

/*
 * cache的“构造”和“析构”函数
 * 动态分配、回收cache
 * 使用calloc而非malloc来初始化内存
 */
Cache cache_constructor(int s, int E, int b) {
    int S = 1 << s;
    int B = 1 << b;

    Cache cache;
    // cache组
    cache.sets = (Cache_set*)calloc(S, sizeof(Cache_set));
    // 每组的行
    for (int i = 0; i < S; i++) {
        cache.sets[i].lines = (Cache_line*)calloc(E, sizeof(Cache_line));
        // 每行的块
        for (int j = 0; j < E; j++) {
            cache.sets[i].lines[j].blocks = (char*)calloc(B, sizeof(char));
            // 由于使用calloc,所以就不需要对每一行都初始化了
        }
    }
    return cache;
}
void cache_destructor(Cache *cache_ptr, int s, int E, int b) {
    int S = 1 << s;
    for (int i = 0; i < S; i++) {
        for (int j = 0; j < E; j++)
            free(cache_ptr->sets[i].lines[j].blocks);
        free(cache_ptr->sets[i].lines);
    }
    free(cache_ptr->sets);
}

/*
 * 递增每行的LRU_tag位
 */
void incLRUtag(int s, int E, Cache *cache_p) {
    int S = 1 << s;
    for (int i = 0; i < S; i++)
        for (int j = 0; j < E; j++)
            cache_p->sets[i].lines[j].LRU_tag++;
}

int main(int argc, char *argv[]) {
    // 检查并输入命令行参数
    char c; // 单个字符
    int s, E, b;    // cache参数
    int v = 0;	// v = 1表示verbose模式
    char *file = NULL;  // trace文件路径
    
    while ((c = getopt(argc, argv, "hvs:E:b:t:")) != -1) {
        switch (c) {
        case 'v':
            v = 1;
            break;
        case 's':
            s = atoi(optarg);
            break;
        case 'E':
            E = atoi(optarg);
            break;
        case 'b':
            b = atoi(optarg);
            break;
        case 't':
            file = optarg;
            break;
        case 'h':
        default:
            print_help(argv);
            return 0;
        }
    }

    // 打开并读取tracee文件
    FILE *fp = NULL;
    // 如果文件读取失败
    if ((fp = fopen(file, "r")) == NULL) {
        printf("%s: No such file or directory\n", file);
        return 0;
    }

    // 创建一个cache
    Cache cache = cache_constructor(s, E, b);
    // hit、miss和eviction数量
    int hit_N = 0, miss_N = 0, evic_N = 0;

    // 循环读取trace文件里除了I(instruction load)的其他指令
    while ((c = fgetc(fp)) != EOF) {
        char str[30];	// 存储每行的字符串
        // 除了I之外的每个指令都有一个前置空格
        if (c != ' ') {
            // 跳过该行
            fgets(str, 30, fp);
            continue;
        }
        // 其他指令
        c = fgetc(fp);	// 读取指令字符到c
        fgetc(fp);	// 跳过指令字符后的空格
        fgets(str, 30, fp);	// 读取剩下的格式为"address,size"的内容到str
        
        // 题目说可以忽略size,读取的地址存在address_str里
        char *address_str = NULL;
        address_str = strtok(str, ",");
        /*
         * strtol()的第二个参数是char **endptr,它保存着截断处的字符串地址
         * 如strtol(str, &size_str, 16);即保存','开始的后面的字符串
         * 所以可以使用这个特性来保存字符串size,但为了方便就先没有实现这个
         */
        long address = strtol(address_str, NULL, 16);
        // 使用unsigned long而非long是为了获得标记和组索引时使用逻辑右移而非算术右移,这样的话不会被符号位影响
        unsigned long tag = address >> (s + b);	// the tag bits
        // 这里的setIndex是指集合的索引,不是设置索引
        unsigned long setIndex = (unsigned long)address << (64 - s - b) >> (64 - s);
        //long blockOffsef = address << (64 - b) >> (64 - b);
        int flag = -1;	// flag = 1指hit, = 0指miss, = 2指eviction
        
        // 访问cache
        for (int i = 0; i < E; i++) {
            // 命中时
            if (cache.sets[setIndex].lines[i].tag == tag && cache.sets[setIndex].lines[i].is_valid == 1) {
                // 设置LRU标记位和flag
                incLRUtag(s, E, &cache);
                cache.sets[setIndex].lines[i].LRU_tag = 0;
                flag = 1;
                break;
            }
        }
        // 不命中,存到cache中
        if (flag != 1) {
            for (int i = 0; i < E; i++) {
                // 如果有空行,就写到这里
                if (cache.sets[setIndex].lines[i].is_valid == 0) {
                    incLRUtag(s, E, &cache);
                    cache.sets[setIndex].lines[i].LRU_tag = 0;
                    cache.sets[setIndex].lines[i].is_valid = 1;
                    cache.sets[setIndex].lines[i].tag = tag;
                    flag = 0;
                    break;
                }
            }
            // 否则使用LRU策略替换
            if (flag != 0) {
                int maxLRUline = 0;
                for (int i = 0; i < E; i++)
                    if (cache.sets[setIndex].lines[i].LRU_tag > cache.sets[setIndex].lines[maxLRUline].LRU_tag)
                        maxLRUline = i;
                incLRUtag(s, E, &cache);
                cache.sets[setIndex].lines[maxLRUline].LRU_tag = 0;
                cache.sets[setIndex].lines[maxLRUline].is_valid = 1;
                cache.sets[setIndex].lines[maxLRUline].tag = tag;
                flag = 2;
            }
        }
        
        // S、L和M都有相同的行为(M多加一次一定命中的访问)
        // 根据flag递增hit、miss和evition的数量
        switch (flag) {
        case 1:
            hit_N++;
            break;
        case 0:
            miss_N++;
            break;
        case 2:
            miss_N++;
            evic_N++;
            break;
        }
        
        // verbose模式
        if (v == 1) {
            printf("%c %s, ", c, address_str);
            switch (flag) {
            case 1:
                printf("hit");
                break;
            case 0:
                printf("miss");
                break;
            case 2:
                printf("eviction");
                break;
            }
        }
        
        // 如果是指令M,那么第二次访问一定命中
        if (c == 'M') {
            hit_N++;
            if (v == 1) {
                printf(" hit\n");
            }
        }
        else
            printf("\n");
    }

    // 释放资源
    fclose(fp);
    cache_destructor(&cache, s, E, b);

    printSummary(hit_N, miss_N, evic_N);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值