【深入理解计算机系统】CSAPP-实验五:CacheLab(未完成)

前言

本章帮助理解告诉缓存对程序性能的影响。包含两个部分:

第一部分是写两三百行代码来模拟高速缓存。

第二部分是优化矩阵转置计算,使得cache miss次数最少。

本机使用win10 +wsl2.0 + ubuntu18.04完成实验。

点击查看我的全部代码

倒是CMU的官网打开了。两天了。

从github上下载了实验数据。

reference

CSAPP-Labs-实验材料-含有PDF

CSAPP-Labs-实验材料-含有源文件

深入理解计算机系统-cachelab

CSAPP-cachelab 解题思路记录

linux环境valgrind 安装

PART A

任务:在csim.c中完成cache模拟器的编码。自己写的程序需要输出与目标文件一样。cache使用LRU策略。

linux> ./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

命令行参数

Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>

其中各参数意义如下:

①-h:输出帮助信息的选项;

②-v:输出详细运行过程信息的选项;

③-s:组索引的位数(意味着组数S=2^s);

④-E:每一组包含的行数

⑤-b:偏移位的宽度(意味着块的大小为B=2^b);

⑥-t:输入数据文件的路径(测试数据从该文件里面读取)。

指令解析

Instruction NameMeaningDescription
Iinstruction loadan instruction load 不用管
Ldata loaddata load 即访问一次
Sdata storedata store 即访问一次
Mdata modifydata modify (i.e., a data load followed by a data store). 即访问两次

每个“I”前面都没有空格。每个“M”,“L”和“S”之前总是有空格。

这里解释一下traces/yi.trace的执行过程。(S,E,B,m) :(S = 2^4,E = 1,B = 2^4, 64) .

即共有16个组,每个组只有一行(退化为直接映射高速缓存)。

b=4, B = 16

s=4, S = 16

t = m - b - s = …

yi.trace

格式:Instruction name - address - size

 L 10,1
 M 20,1
 L 22,1
 S 18,1
 L 110,1
 L 210,1
 M 12,1

①对于地址0x10进行访问:

0x10=0000…00010000,偏移值为最低四位,故S=1;

访问结果为mis;

②连续对地址0x20进行连续两次访问:

0x20=000…00100000,S=2;

结果为第一次mis,第二次hit;

③对地址0x22进行访问:

0x22=000…00100100,S=2;

由于操作②以将该块存入高速缓存,故结果为hit;

④对地址0x18进行访问:

0x18=000…00011000,S=1;

由于操作①以将该块存入高速缓存,故结果为hit;

⑤对地址0x110进行访问:

0x110=0…000100010000,S=1;

虽然操作①使得第一组(只有一行有效),但是这里的标志位的值Tag为1

故结果为先mis,后eviction;

⑥对地址0x210进行访问:

0x210=0…001000010000,S=1;

同操作⑤,但是这里的标志位的值为2,不匹配

故结果为先mis,后evicton;

⑦对地址0x12进行连续两次访问:

0x12=000…00000010010,S=1;

由于标志位不匹配,故第一次访问时mis,并evicton

第二次访问时当然就是hit。

可以确定:

  1. 同一组缓存之后,无论它的块偏移是多少,都会hit。

  2. 只要是M操作(访问两次),第二次一定是hit。

    Each data load (L) or store (S) operation can cause at most one cache miss. The data modify operation(M) is treated as a load followed by a store to the same address. Thus, an M operation can result in two cache hits, or a miss and a hit plus a possible eviction.

思路

PART A只是对组相联高速缓存的简单的模拟,没有时间空间复杂度要求,没有什么难度。只是对代码

程序显然是这个流程进行的:

  1. 命令行的解析。解析后需要初始化某些超参数。
  2. 读取文件,每行每行进行结果处理。
  3. 将最后结果打印。

代码实现

调用

sudo ./csim -v -s 4 -E 1 -b 4 -t traces/dave.trace

sudo ./csim-ref -v -s 4 -E 1 -b 4 -t traces/dave.trace

输入

 L 10,4 
 S 18,4
 L 20,4
 S 28,4
 S 50,4

步骤1:命令行解析

    //命令行解析部分
    int verbose = 0;
    int s,E,b;
    char* t;
    int ch;
    while ((ch = getopt(argc, argv, "s:E:b:t:v")) != -1){
         switch (ch) {
            case 's':
                s = atoi(optarg);
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                t = optarg;
                break;
            case 'v':
                verbose = 1;
                break;
            default:
                exit(-1);
         }
    }

    printf("输入命令行:s:%d E:%d b:%d verbose:%d t:%s\n",s,E,b,verbose,t);
  int S = 1<<s;
  int B = 1<<b;

输出如下

输入测试:s:4 E:1 b:4 verbose:1 t:traces/dave.trace

步骤2:读取文件

    //读取文件
    FILE* fp = fopen(t, "r");
    if(fp == NULL)
    {
      printf("%s: No such file or directory\n", t);
      exit(1);
    }
    /*
        定义数据结构;读入数据;拟真
    */
    int type,address,size;
    while(fscanf(fp, " %c %lx,%d", &type, &address, &size) != EOF)
    {
      if(type == 'I'){
          continue;
      }
      else{
          //接口进入
      }
    }

步骤3:拟真

  1. 每行含有:一个有效位、t个标记位,b个高速缓存位。以及支持LRU的时间。可以定义一个struct支持。

  2. 有S个组,每个组有E行。可以定义一个二维数组。

详细请查看代码吧。注释写得很详细。

/*
    NAME: Jaxchan   
    GITHUB: Jaxchan25
*/
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "getopt.h"
#include "time.h"
#include "cachelab.h"

int hits = 0 ,misses = 0 ,evictions = 0; 
int DEBUG = 0;

typedef  struct
{
  int valid;//有效位
  unsigned long tag; //标记位
    //这里是否需要存高速缓存块呢
  clock_t time_stamp;

}cache_line;

/*
  @brief: 初始化二维数组,动态分配内存
  @param:S 多少个组
  @param:E 每组有多少个行
*/
cache_line** initiate(int S,int E){
  cache_line** cache;
  cache = (cache_line** ) malloc(sizeof(cache_line) * S); 
    for(int i=0;i<S;i++)
    {
      int size = sizeof(cache_line) * E;
      cache[i] = (cache_line* ) malloc(size);
      memset(cache[i], 0, size);
    }
  return cache;
}

/*
  清理二维数组
*/
int clean(cache_line** c, int S){
  /* cache_line 二维数组的清理工作 */
  int i;
  for(i=0;i<S;i++)
    {
      free(c[i]);
    }
  free(c);
  return 0;
}


int isHit
(
  cache_line* cache_line_group,
  int E,
  unsigned long t
){

  //DEBUG
  if(DEBUG){
    printf("\n DEBUG IN isHit \t");
    for(int i =0;i<E;i++){
        printf("\n INDEX: %d ",i);
        printf(" FOUND USING TAG: %lx \t",cache_line_group[i].tag);
    }
    printf("\nEND DEBUG IN isHit\n");
  }

  for(int i =0;i<E;i++){
    //hit:有效位 =1 ,且标记位对得上
    if (cache_line_group[i].valid==1&&cache_line_group[i].tag == t){
      cache_line_group[i].time_stamp = clock();
      hits+=1;
      printf(" FOUND USING TAG: %lx \t",t);
      return 1;
    }
  }
  return 0;
}

int putInCache(
  cache_line* cache_line_group,
  int E,
  unsigned long t
){

  //直接可以放在空位。
  for(int i =0;i<E;i++){
    //hit:有效位 =1 ,且标记位对得上
    if (cache_line_group[i].valid==0){
      cache_line_group[i].valid = 1;
      cache_line_group[i].tag = t;
      cache_line_group[i].time_stamp = clock();
      printf(" PLACE IN BLANK USING TAG: %lx \t",t);
      printf(" UPDATE TAG: %lx\t",cache_line_group[i].tag);
     return 0;
    }
  }

  //需要进行evictions
  //搜索
  int LRU_index = 0;
  clock_t LRU_time_stamp = cache_line_group[0].time_stamp; //时间越久,数值越少
  for(int i =0;i<E;i++){
    if (cache_line_group[i].time_stamp<LRU_time_stamp){
      LRU_index = i;
      LRU_time_stamp = cache_line_group[i].time_stamp;
    }
  }
  //进行
  cache_line_group[LRU_index].tag = t;
  cache_line_group[LRU_index].time_stamp = clock();
  evictions+=1;
  printf(" PLACE BY EVICTIONS USING TAG: %lx\t",t);
  printf(" UPDATE TAG: %lx\t UPDATE INDEX: %d\t",
    cache_line_group[LRU_index].tag,LRU_index);
  return 1;
}

void print_verbose(char* pre, char type, int hit_miss, int eviction){
  /* 命令行带 -v 的话的详细数据输出函数 */
  char* h = hit_miss?" hit":" miss";
  char* e = eviction?" eviction":"";
  char* format;
  if(type == 'M')
    { 
      //如果是M模式,最后一定是hit
      format = "%s%s%s\n";
      strcat(pre, format);
      printf(pre, h, e, " hit");
    }
  else
    { 
      format = "%s%s\n";
      strcat(pre, format);
      printf(pre, h, e);
    }
}


/*
  @brief: 访存核心部分。处理各个指令,判断是否hit\miss\evictions
  @param: cache 缓存,二维数组表示
  @param: type,命令指令类型,分别为L\S\M
  @param: address,命令访问地址,64位长度hex
  @param: size,命令访问块的大小,单位字节。这里不影响解题,只是输出就好。
  @param: b_mask s_mask t_mask都是cache本身的超参数,做成了mask是方便计算。
  @param: E,b 是cache本身的超参数。
  @param: hits misses evictions 击中\丢失\替换的累计值
*/
void handleCore(
  cache_line** cache,
  char type,
  unsigned long address,
  int size,
  unsigned long b_mask,
  unsigned long s_mask,
  unsigned long t_mask,
  int E,
  int b,
  int verbose
){
  //对地址解析
  unsigned long t = address&t_mask; 
  unsigned long s =  address&s_mask;

  //取出组
  unsigned long s_id = s>>b;
  cache_line* cache_line_group = cache[s_id];
  printf("组号:%lu ,tag: %lx    \t",s_id,t);

  //查看是否hit,如果hit直接更新时钟。
  int flag_hit = isHit(cache_line_group,E,t);

  //如果没有hit,就要evict或放入空位置
  int flag_evict = 0;
  if(flag_hit==0){
    misses+=1;
    flag_evict = putInCache(cache_line_group,E,t);
  }

  //如果指令是M,需要额外+1个hit
  if(type=='M'){
    hits+=1;
  }

  //输出一下
  if(verbose)
  { char pre[20];
    sprintf(pre, "%c %lx,%d", type, address, size);
    print_verbose(pre, type, flag_hit, flag_evict);
  }


}




int main(int argc, char * argv[])
{      
  //命令行解析部分
  int verbose = 0;
  int s,E,b;
  char* t;
  int ch;
  while ((ch = getopt(argc, argv, "s:E:b:t:v")) != -1){
        switch (ch) {
          case 's':
              s = atoi(optarg);
              break;
          case 'E':
              E = atoi(optarg);
              break;
          case 'b':
              b = atoi(optarg);
              break;
          case 't':
              t = optarg;
              break;
          case 'v':
              verbose = 1;
              break;
          default:
              exit(-1);
        }
  }
  printf("输入命令行:s:%d E:%d b:%d verbose:%d t:%s\n",s,E,b,verbose,t);
  int S = 1<<s;
  //int B = 1<<b;
  /*
    初始化二维数组
  */
  cache_line** cache;
  cache = initiate(S,E);


  //读取文件
  FILE* fp = fopen(t, "r");
  if(fp == NULL)
  {
    printf("%s: No such file or directory\n", t);
    exit(1);
  }
  /*
      定义数据结构;读入数据;拟真
  */

  unsigned long b_mask = 1<<b; 
  b_mask -=1; //0000...0 00001111

  unsigned long s_mask = 1<<(s+b) ;
  s_mask = (s_mask-1)^ b_mask;//0000...0 11110000

  unsigned long t_mask = 1<<(s+b);
  t_mask = (t_mask - 1 )^(~0);//1111...1 00000000

  printf("b_mask: %lx ",b_mask);
  printf("s_mask: %lx ",s_mask);
  printf("t_mask: %lx \n",t_mask);

  char type;
  int size;
  unsigned long address;
  while(fscanf(fp, " %c %lx,%d", &type, &address, &size) != EOF)
  {
    if(type == 'I'){
        continue;
    }
    else{
        //接口进入
        handleCore(cache,type,address,size,b_mask,s_mask,t_mask,E,b,verbose);

    }
  }

  printSummary(hits, misses, evictions);
  clean(cache,S);
  fclose(fp);
  return 0;
}

测试

tset-case1:
sudo ./csim -v -s 4 -E 1 -b 4 -t traces/dave.trace
sudo ./csim-ref -v -s 4 -E 1 -b 4 -t traces/dave.trace

test-case2:
sudo ./csim -v -s 4 -E 2 -b 4 -t traces/yi.trace
sudo ./csim-ref -v -s 4 -E 2 -b 4 -t traces/yi.trace
make
sudo ./test-csim

在这里插入图片描述

这样27分就是满分了。

PART B

任务

在trans.c中完成矩阵转置函数transpose_submit,并且越少的Miss越好。

限制:

  1. 最多使用12 local variables int。不能作弊。
  2. 不可递归。
  3. 用helper函数的话,栈总共不超12 variables
  4. A不许修改。
  5. 不允许用数组和malloc.

评估

评估方法:

• 32 × 32: 8 points if m < 300, 0 points if m > 600
• 64 × 64: 8 points if m < 1300, 0 points if m > 2000
• 61 × 67: 10 points if m < 2000, 0 points if m > 3000

思路

cache参数:(s = 5, E = 1, b = 5,m=64) 。

即32组,每组一行,每行存32字节,即8个int。

所以一共可以存32 * 8 = 256个int。

要知道的是:

  1. 每次加载,都是加载线性空间的Block,Block大小就是缓存B的大小。

测试

sudo make
sudo ./test-trans -M 4 -N 4
sudo ./test-trans -M 32 -N 32
sudo ./test-trans -M 64 -N 64
sudo ./test-trans -M 61 -N 67
sudo ./csim-ref -v -s 5 -E 1 -b 5 -t trace.f0

参考

不错的解析:

Reference 1

Reference 2

思路

从小的开始:4X4

昨天我认真又看了一下书以及习题。PART B事实上就是书本练习6.17.

于是我们把PART A,自己写的工具用来测试一下。参数和PART B 要求不变,仍然是(s = 5, E = 1, b = 5,m=64)。输入矩阵则是4 x 4。

我们可以任意输出debug信息。

sudo make
sudo ./test-trans -M 4 -N 4
sudo ./csim -v -s 5 -E 1 -b 5 -t trace.f0

又有Reference 1的解析,我们可以知道:

  1. miss过多的原因是,两者一开始的访问index很接近,又都加载到cache的同一行,所以会不断的eviction,从而发生抖动。
  2. 所以,我们的最小单位起码是block,即在src读取一个block之后,一次性全部加载到寄存器(local variables)中;然后再写入dst。

后面的详解,我查看了大佬们的解答,觉得很有学问,暂时无法点评。

然后我就又暂时放下了这个实验。

后话

这次实验很难。PART A是从无到有写一个bug-free代码,挺考验功底的。

PART B则是高难度的性能分析了,需要很熟悉。后面如果碰到对这里有要求,会再回来嗑这个实验。

由于做得太菜,就不要留言交流了。

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 深入理解计算机系统(CSAPP)是由Randal E. Bryant和David R. O'Hallaron编写的经典计算机科学教材。该教材通过涵盖计算机体系结构、机器级别表示和程序执行的概念,帮助学生深入理解计算机系统的底层工作原理和运行机制。 深入理解计算机系统的练习题对于学生巩固并应用所学知识非常有帮助。这些练习题涵盖了计算机硬件、操作系统和编译器等多个领域,旨在培养学生解决实际问题和设计高性能软件的能力。 对于深入理解计算机系统的练习题,关键是通过实践进行学习。在解答练习题时,应根据课本提供的相关知识和工具,仔细阅读问题描述,并根据实际需求设计相应的解决方案。 在解答练习题时,需要多角度思考问题。首先,应准确理解题目要求,并设计合适的算法或代码来解决问题。其次,应考虑代码的正确性和效率,以及对系统性能的影响。此外,还要注意处理一些特殊情况和异常情况,避免出现潜在的错误或安全漏洞。 解答练习题的过程中,应注重查阅相关资料和参考优秀的解答。这可以帮助我们扩展对问题的理解,并学习他人的思路和解决方法。同时,还可以通过与同学和老师的讨论,共同探讨问题和学习经验。 总之,通过解答深入理解计算机系统的练习题,可以帮助学生巩固所学知识,同时培养解决实际问题和设计高性能软件的能力。这是一个学以致用的过程,可以加深对计算机系统运行机制和底层工作原理的理解。 ### 回答2: 理解计算机系统(CSAPP)是一本经典的计算机科学教材,通过深入研究计算机系统的各个方面,包括硬件、操作系统和编程环境,对于提高计算机科学专业知识与能力具有很大帮助。 练习题是CSAPP中的重要部分,通过练习题的完成,可以加深对计算机系统的理解,并将理论知识转化为实践能力。练习题的数量、难度逐渐递增,从简单的概念与基础问题到复杂的系统设计与实现。 在解答练习题时,首先需要对题目进行仔细阅读和理解,明确题目的要求和限制条件。然后,可以利用课堂讲解、教材内容、网络资源等进行查阅和学习相应的知识。同时,还可以参考课后习题解答等资料,了解一些常见的解题方法和思路。 在解答练习题时,可以利用计算机系统的工具和环境进行实际测试和验证。例如,可以使用调试器、编译器和模拟器等工具对程序或系统进行分析和测试。这样可以更加深入地理解问题的本质,并找到恰当的解决方法。 另外,解答练习题时还可以与同学、教师和网上社区进行交流和讨论。这样可以互相学习和交流解题思路,共同解决问题。还可以了解不同的解题方法和技巧,提高解题效率和质量。 练习题的解答过程可能会遇到一些困难和挑战,例如理论知识的不足、复杂问题的分析与解决。但是通过不断地思考和实践,相信可以逐渐提高解题能力,更好地理解计算机系统。 总之,深入理解计算机系统(CSAPP)练习题是提高计算机科学专业知识和能力的重要途径。通过仔细阅读和理解题目,查阅相关知识,利用计算机系统工具和环境进行实践,与他人进行交流和讨论,相信可以更好地理解计算机系统的各个方面,并将知识转化为实际能力。 ### 回答3: 《深入理解计算机系统(CSAPP)》是计算机科学领域的经典教材之一,对于深入理解计算机系统的原理、设计和实现起到了极大的帮助。在阅读这本书的过程中,书中的习题也是非常重要的一部分,通过做习题,我们可以更好地理解书中所讲的概念和思想。 CSAPP的习题涵盖了课本中各个章节的内容,从基础的数据表示和处理、程序的机器级表示、优化技术、程序的并发与并行等方面进行了深入探讨。通过解答习题,我们可以对这些知识进行实践应用,巩固自己的理解,并培养自己的解决问题的思维方式。 在解答习题时,我们需要充分理解题目要求和条件,并从知识的角度进行分析。有些习题可能需要进行一些编程实践,我们可以通过编程实现来验证和测试我们的思路和解决方案。在解答问题时,我们还可以查阅一些参考资料和网上资源,充分利用互联网的学习资源。 在解答习题时,我们需要保持积极的思维和态度。可能会遇到一些困难和挑战,但是通过坚持和努力,我们可以克服这些困难,提高我们的解决问题的能力。同时,我们还可以通过与同学或者其他人进行讨论,相互分享解题经验和思路,从而更好地理解问题。 综上所述,通过深入理解计算机系统(CSAPP)的习题,我们可以进一步巩固和深化对计算机系统的理解。掌握这些知识,不仅可以提高我们在计算机领域的能力,还可以为我们来的学习和职业发展奠定重要的基础。因此,认真对待CSAPP的习题,是我们在学习计算机系统知识中不可或缺的一部分。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值