Lab4 Cache Lab

Lab4 Cache Lab

CSAPP | Lab5-Cache Lab 深入解析 - 知乎 (zhihu.com)<——写得挺好的,代码全靠这个帖子

这篇文章就只写了一半,别看了,即使止损。

一、做什么

说明文档:cachelab.dvi (cmu.edu)

下载解压就不用说了,然后我们的工作在csim.c和trans.c两个文件中做,以下命令编译文件

make clean
make

实验分为两个部分。在 A 部分,你将实现一个缓存模拟器。在 B 部分,你将编写一个针对缓存性能进行优化的矩阵转置函数。

CMU 关于 Cache Lab 的 PPT:rec07.pdf (cmu.edu)

二、Part A: Writing a Cache Simulator

  1. reference trace files

目录下的trace子目录包含的文件用于评估在 A 部分编写的缓存模拟器正确性,由一个名为 valgrind 的 Linux 程序生成的

linux> valgrind --log-fd=1 --tool=lackey -v --trace-mem=yes ls -l

运行后有以下的打印:
I 0400d7d4,8
M 0421c7f0,4
L 04f6b868,8
S 7ff0005c8,8

运行可执行程序 “ls -l”,捕获其每次内存访问并按其发生顺序打印到 stdout 上。每一行表示一个或两个内存访问,格式为[空格]操作 地址,大小。

操作字段表示内存访问的类型:“I”表示指令加载,“L”表示数据加载,“S”表示数据存储,“M”表示数据修改(即,数据加载后跟随数据存储)。在每个“I”前面从不加空格。在每个“M”、“L”和“S”前面总是有一个空格。地址字段指定一个 64 位十六进制内存地址。大小字段指定操作访问的字节数。

  1. 做什么

在csim.c编写一个1缓存模拟器,以Valgrind的内存跟踪作为输入,模拟缓存内存在这个跟踪中的命中/未命中行为,并输出命中、未命中和驱逐的总数

一个例子:csim-ref,是一个二进制文件,尝试输入下./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace,就会发现有个统计。我们的目的就是编写csim.c实现像csim-reg一样的效果( takes the same command line arguments and produces the identical output as the reference simulator)

用法: ./csim-ref [-hv] -s -E -b -t

• -h: 可选的帮助标志,用于打印使用信息 
• -v: 可选的详细标志,显示跟踪信息 
• -s <s>: 组索引位数(S = 2^s是组数) 
• -E <E>: 关联度(每组的行数) 
• -b <b>: 块位数(B = 2^b是块大小) 
• -t <tracefile>: 要回放的Valgrind跟踪文件的名称

3.Programming Rules for Part A

翻译的,挑了下重点

• 你的 csim.c 文件必须能够在编译时不产生警告,才能获得分数。
• 你的模拟器必须对任意的 s、E 和 b 值都能正确运行。这意味着你需要使用 malloc 函数为模拟器的数据结构分配存储空间。可以使用 man malloc 命令查看有关该函数的信息。
• 对于这个实验,我们只关心数据缓存的性能,所以你的模拟器应该忽略所有指令缓存的访问(以“I”开头的行)。请记住,Valgrind 总是将“I”放在第一列(没有前导空格),而“M”、“L”和“S”放在第二列(有前导空格)。这可能有助于你解析跟踪文件。
• 要获得 Part A 的分数,你必须在 main 函数的末尾调用 printSummary 函数,并传入总命中次数、未命中次数和驱逐次数:printSummary(hit_count, miss_count, eviction_count);
• 对于这个实验,你应该假设内存访问已经正确对齐,使得单个内存访问永远不会跨越块边界。通过做出这个假设,你可以忽略 Valgrind 跟踪中的请求大小。

4.实现

①处理命令行参数

(这里是因为我的argc和argv不熟悉)再想一下例子的执行命令,./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace,就可以知道argc和argv中是什么

PPT上写的建议用getopt得到参数,使用前注意包括 #include <unistd.h>,#include <getopt.h>

#include <unistd.h>
#include <getopt.h>

int verbose = 0;    // 全局变量,是否打印过程
char t[100];        // 要回放的Valgrind跟踪文件的名称

int main(int argc,char *argv)
{
	char opt;
	int s,E,b;
	while(-1 != (opt=getopt(argc,argv,"hvs:E:b:t:")))
    {
        switch(opt)
        {
        case 'h':
            print_help();
            exit(0);
        case 'v':
            verbose = 1;
            break;
        case 's':
            s = atoi(optarg);
            break;
        case 'E':
            E = atoi(optarg);
            break;
        case 'b':
            b = atoi(optarg);
            break;
        case 't':
            strcpy(t,optarg);
            break;
        default:
            print_help();
            exit(1);

        }
    }
}

②得到输入和初始化缓存池,以及整个思路

以Valgrind的内存跟踪作为输入,先看一下输入是什么,类似于这种"I 0400d7d4,8",格式为[空格]操作 地址,大小。在t里面的,就相当于打开文件读取操作内容。

前面得到s、E、b就可以初始化缓存了。如图
在这里插入图片描述
记住我们的目的,写一个程序,模拟缓存的操作。所以只需要在main函数后面完成下面四个函数就行

Init_Cache(s, E, b); //初始化一个cache 
get_trace(s, E, b);//模拟操作
free_Cache();// 释放cache
printSummary(hit_count, miss_count, eviction_count);

设为全局变量方便调用

int hit_count = 0, miss_count = 0, eviction_count = 0; // 记录冲突不命中、缓存不命中
int verbose = 0;                                       //是否打印详细信息
char t[1000];
Cache *cache = NULL;

先完成Init_Cache

typedef struct cache_line
{
    int valid;
    int tag;
    int time_tamp;  // 时间戳
}Cache_line;
typedef struct
{
    int S;
    int E;
    int B;
    Cache_line **line;
}Cache;
void Init_Cache(int s,int E,int b)
{
    int S = 1<<s;
    int B = 1<<b;
    cache = (Cache*)malloc(sizeof(Cache));
    cache->S = S;
    cache->E = E;
    cache->B = B;
    cache->line = (Cache_line **)malloc(sizeof(Cache_line *)*S);
    for(int i=0;i<S;i++)
    {
        cache->line[i] = (Cache_line *)malloc(sizeof(Cache_line)*E);
        for(int j=0;j<E;j++)
        {
            cache->line[i][j].valid = 0;
            cache->line[i][j].tag = -1;
            cache->line[i][j].time_tamp = 0;
        }
    }
}

③get_trace(s, E, b)模拟操作的实现

分缓存命中、不命中(又分冷不命中、confict)

不命中时还要考虑替换策略,冷不命中还好,就直接替换valid=0部分。confict这里考虑LRU,选择最近最少被使用的数据进行替换,所以之前cache里面有一个时间戳表示调用次数。注意,这里不用考虑数据,我们只需要想着这个行为就行。

void update(int index,int op_s,int op_tag)
{
    cache->line[op_s][index].valid = 1;
    cache->line[op_s][index].tag = op_tag;
    for(int k=0;k<cache->E;k++)
        if(cache->line[op_s][k].valid==1)
            cache->line[op_s][k].time_tamp++;
    cache->line[op_s][index].time_tamp = 0;
}
int find_LRU(int op_s)
{
    int max_index = 0;
    int max_stamp = 0;
    for(int i=0;i<cache->E;i++)
    {
        if(cache->line[op_s][i].time_tamp>max_stamp)
        {
            max_stamp = cache->line[op_s][i].time_tamp;
            max_index = i;
        }
    }

    return max_index;
}
void update_info(int op_s,int op_tag)
{
    // 得到第op_s的set的哪一个
    int index = -1;
    for(int i=0;i<cache->E;i++)
    {
        if(cache->line[op_s][i].valid&&cache->line[op_s][i].tag==op_tag)
            index = i;
    }

    if(index==-1)   // 不命中
    {
        miss_count++;
        if(verbose) printf("miss ");

        // 下面是不命中分类
        for(int i=0;i<cache->E;i++)
            if(cache->line[op_s][i].valid==0)
            {
                index = i;  // 冷不命中
                break;
            }
        

        if(index==-1)   // Conflict不命中
        {
            eviction_count++;
            if(verbose) printf("eviction");

            index = find_LRU(op_s);
        }

        update(index,op_s,op_tag);  // 这里index为要替换位置
    }else   // 命中
    {
        hit_count++;
        if(verbose) printf("hit");

        update(index,op_s,op_tag);  // 增加时间戳
    }
}

然后就是根据M、L、S特定的

void get_trace(int s,int E,int b)
{
    // 得到三个输入分别是 操作 地址,大小
    FILE *pFile = fopen(t,'r');
    if(pFile == NULL)   
    {
        exit(-1);
    }
    char op;
    unsigned address;
    int size;
    while(fscanf(pFile," %c %x,%d",&op,&address,&size)>0)
    {
        // 得到s、tag
        int op_s = (address >> b) & ((unsigned)(-1) >> (8 * sizeof(unsigned) - s));
        int op_tag = address >> (s + b);
        switch(op)  // 指令加载与缓存无关
        {
        case 'M':   // “M”表示数据修改,一次存储,一次加载
            update_info(op_tag, op_s);
            update_info(op_tag, op_s);
            break;
        case 'L':   // “L”表示数据加载
            update_info(op_tag,op_s);
            break;
        case 'S':   // “S”表示数据存储
            update_info(op_tag,op_s);
            break;
        }
    }

    fclose(pFile);
}

④其他

剩下的就是一些辅助函数,比如打印提示信息和释放内存空间

void free_Cache()
{
    int S = cache->S;
    for(int i=0;i<S;i++)
    {
        free(cache->line[i]);
    }
    free(cache->line);
    free(cache);
}

void print_help()
{
    printf("** A Cache Simulator by Deconx\n");
    printf("Usage: ./csim-ref [-hv] -s <num> -E <num> -b <num> -t <file>\n");
    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\n\n");
    printf("Examples:\n");
    printf("linux>  ./csim -s 4 -E 1 -b 4 -t traces/yi.trace\n");
    printf("linux>  ./csim -v -s 8 -E 2 -b 4 -t traces/yi.trace\n");
}

5.测试结果

make clean
make
./test-csim

三、Part B: Optimizing Matrix Transpose

  1. 做什么

在trans.c中编写一个转置函数,使缓存错失尽可能少。

char trans_desc[] = "Simple row-wise scan transpose";
void trans(int M, int N, int A[N][M], int B[M][N])

trans.c中给出了一个示例转置函数,用于计算N×M矩阵A的转置,并将结果存储在M×N矩阵B中。答案正确但效率低下、

B部分的任务是编写一个类似的函数,称为transpose_submit。

cache中s=5,E=1,b=5每组一行,每行存 8 个int。测试分别是32*32、64*64、61*67

  1. 编译规则
  • 您在trans.c中的代码必须无警告地编译才能获得学分。
  • 每个转置函数中允许最多定义12个int类型的局部变量。
  • 不允许通过使用任何long类型的变量或使用任何位操作技巧将多个值存储到单个变量中来规避上述规则。
  • 您的转置函数不得使用递归。
  • 如果选择使用辅助函数,则在辅助函数和顶层转置函数之间的堆栈上最多不得有12个局部变量。例如,如果您的转置声明了8个变量,然后调用一个使用4个变量的函数,再调用另一个使用2个变量的函数,您的堆栈上将有14个变量,并且将违反规则。
  • 您的转置函数不得修改数组A。但您可以随意处理数组B的内容。
  • 不允许在代码中定义任何数组或使用malloc的任何变体。
  1. 实现

①理解分块

CSAPP | Lab5-Cache Lab 深入解析 - 知乎 (zhihu.com)

666

小结

​ 害,这个实验我完全是依靠别人的贴子写的。别看,要脸的。要是问我为啥还发出来,就算是插个眼。中间我还跳了个lab3。因为当时没时间了,而且卡了好久。就突然不知道为什么突然就不想做了,但是…

​ CSAPP看了太久了,这学期都快结束了。下个实验我一定好好写。

  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值