CSAPP:cachelab(1)

  本项目大体上就是要求用C\C++来模拟cpu对cache的访问,然后统计hits、misses和eviction的次数。其实并没有想象中的那么难,感觉完全可以当成一道acm里面的大模拟题。。下面就对这个题目涉及到的一些知识点做下总结:

(一)linux命令行处理

  由于题目要求是在linux下以附加命令参数的方式来执行程序的,所以要对命令行的参数进行一下处理。这里要用到getopt函数,函数原型为:

int getopt(int argc,char** argv,const char* opstring);

我们都知道linux的命令参数分长参数和短参数,而这个函数是用来且只能用来处理短参数的,函数的返回值即为当前调用它所读取到的那个参数(int对应其ASCII码值),其中的opstring是一个短参数集合的字符串,形如:

const char* optstring = "hvs:t:b:E:";

其中每一个字母后面如果加一个冒号代表其必须带有一个附加的参数,不带就是必须没有附加的参数,而如果有两个冒号则是可带可不带参数,还有一点很重要,这些参数也就是字母的顺序是无所谓前后的,只要在这个字符串里就可以。

这个函数的调用要引用<getopt.h>库,该库还为我们提供了几个比较用帮助的全局变量,比较常见的有:optarg、opterror、optind,分别表示当前解析的参数的附加参数、解析是否出错(存在不能识别的opt即不在我们的optstring中,或者该加附带参数的opt没加不该加的加了)从而打印错误信息、下一个要解析的参数的index。我们可以根据自己的需要来利用并且手动的更改这些变量。

比如opterror=1的如果出错程序就会打印错误信息而当其等于0的时候程序就不会打印错误信息。我们通过optarg来获取每次解析参数所得到的附加参数,以字符串的形式返回!

关于命令行就总结这些,还有长参数的解析函数getopt_long(),详情可以去查看linux man page: http://linux.die.net/man/3/getopt_long

实现代码:

    char* trace_file;
    const char* optstring = "hvs:E:b:t";
    char opt;
    //deal with the short-option
    while((opt=getopt(argc,argv,optstring))!=-1) {
        switch (opt) {
            case 'h':
                printusage(argv);
                break;
            case 'v':
                flag = 1; 
                break;
            case 's':
                state.s = atoi(optarg);
                break;
            case 'E':
                state.E = atoi(optarg);
                break;
            case 'b':
                state.b = atoi(optarg);
                break;
            case 't':
                trace_file = optarg;
                break;
            default :
                printusage(argv);
                break;
        }
    }    

 

(二)Cache的初始化

  这部分其实就是通过编程语言的形式来“模拟”出cache的简单模型,即S=2^s个分组(set),每一组有E行,每行有B=2^b个存储单元,还有tag标记位和valid有效位,具体的图片如下图:

(图片来源:CSAPP.2E P305)

第一看这张图的时候一直看不懂,后来明白了上面得灰色部分是cache,而最下面哪一行长条是内存的一个地址,这个原理就是我们对内存的地址按照给定的参数s,E,b(其实还有个m,不过这题默认m=64)来划分成不同的块,从而借此来在cache中查找其是否存在,如果存在就是一个hit,直接在cache中提取从而节省访存的时间,反之就要将其载入cache然后在提取。

至于语言上的实现,就是设置几个结构体然后用malloc分配空间,或者用3维数组来实现应该也可以(不过数据量太大应该就不行了)

代码实现:

Cache init(int s,int E,int b)
{
    int i,j;
    int S = 1<<s;
    int B = 1<<b;
    Cache cache_t;
    Set set_t;
    Line line_t;
    cache_t.s = (Set*)malloc(sizeof(Set)*S);
    for(i = 0;i < S;i++) {
        set_t.l = (Line*)malloc(sizeof(Line)*E);
        cache_t.s[i] = set_t;
        for(j = 0;j < E;j++) {
            line_t.block = (int*)malloc(sizeof(int)*B);
            cache_t.s[i].l[j] = line_t;
            cache_t.s[i].l[j].flag = 0;
            cache_t.s[i].l[j].tag = 0;
            cache_t.s[i].l[j].used_time = 0;
        }
    }
    return cache_t;
}

(三)核心部分:对内存的访问

关于从trace文件中获取的数据,每条有两个要注意,一是开头的命令字母,这点cmu在一开始的提示文档里也给我们指出来了:

注意这个‘M’是相当于两次访存!

第二个就是提供的address了,其实我们每次更新cache只要将address给传入到state_fresh函数就可以,然后根据给定的b、E、s来确定miss和hit的情况:

void state_fresh(Cache* cache,State* state,int ad,int cflag)
{
    int i,j;
    Set set_t;
    Line line_t;
    int cnt = getbi(ad);
    int m = 0;
    int set = 0,tag = 0;
    //get set 
    for(i = cnt-state->b;i > cnt-state->b-state->s;i--)
        set += bi[i]*(1<<(m++));
    //get tag
    m = 0;
    for(i = cnt-state->b-state->s;i > 0;i--) 
        tag += bi[i]*(1<<(m++));

首先对于给定的address我们确定他如果在cache中则他必须在的set的序号和他所固有的tag值,计算出这两个值后剩下的就是在cache中的相应位置去查找即可:

首先给出hit的情况:

//search in cache
    set_t = cache->s[set];
    for(i = 0;i < state->E;i++) {
        line_t = set_t.l[i];
        if(line_t.flag==1 && line_t.tag==tag) {
            state->hit++;
            cache->s[set].l[i].used_time++;
            if(cflag) printf("hit\n");
            return;
        }
    }

如果set匹配,行匹配(flag=1且tag匹配),则标记位hit做好相应的处理工作,这题的关键点就是miss的情况,因为我们要分别讨论其是否要进行evict以及如何进行evict

还是先给出有空位的情况:

//miss-could be flag = 0 or tag not equal
    state->miss++;
    //get the max used number
    int* used_num = (int*)malloc(2*sizeof(int));
    int Min = getnum(&set_t,state,used_num);
    //1.search for empty seats
    for(i = 0;i < state->E;i++) {
        line_t = set_t.l[i];
        if(line_t.flag == 0) {
            cache->s[set].l[i].flag = 1;
            cache->s[set].l[i].tag = tag;
            cache->s[set].l[i].used_time = used_num[1]+1;
            if(cflag) printf("miss\n"); 
            return; 
        }
    }

getnum函数就是找到当前ad所必须在的set的所有行的最小值和最大值,分别存储在used_num[0]和used_num[1]中,最小值很好理解就是为了稍后的找不到空行时替换用的,而最大值则是为了将miss的ad载入cache时标记其优先级用的。

LRU(Least-Recently used)算法就是在evict时主要用到的,这里只是给出了他的低级实现,简单的记录一下优先级,更全的大家可以参考:http://flychao88.iteye.com/blog/1977653 里面从低级到高级的LRU都有讲解,还有java的实现

        //2.evict
    if(cflag) {
        if(cache->s[set].l[Min].flag==1) 
            printf("miss ");
        else 
            printf("miss\n");
    }
    if(cache->s[set].l[Min].flag==1) {
        state->evict++;
        if(cflag) 
            printf("eviction\n");
    }
    cache->s[set].l[Min].flag = 1;
    cache->s[set].l[Min].tag = tag;
    cache->s[set].l[i].used_time = used_num[1]+1;
    free(used_num);    

 

posted @ 2016-06-01 11:27  Miller_S  阅读(10891)  评论(0编辑  收藏  举报