《深入理解计算机系统》实验五Cache Lab

前言

《深入理解计算机系统》实验五Cache Lab下载和官方文档机翻请看:
https://blog.csdn.net/weixin_43362650/article/details/121989400
我觉得这个文档对整个实验很有帮助。

对于我来说实验五Cache Lab中的B部分64*64是实验一~实验五中最难的,我还是打开了百度,害。这矩阵还是看的懵懵懂懂,所以个人认为该篇播客只有A部分有看头。B部分建议看这篇。
深入理解计算机系统-cachelab

A部分

题目请看文档。

基本架构-接收命令行参数

首先应该先实现基本的架构,即可以接收到在linux命令行中输入的-v、-h、-s等参数。
文档有提到建议使用getopt函数来解析命令行参数。需要三个头文件

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

详细可见(以下只提用上的)

linux> man 3 getopt
int getopt(int argc, char * const argv[], const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

有个例子

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int
main(int argc, char *argv[])
{
   int flags, opt;
   int nsecs, tfnd;

   nsecs = 0;
   tfnd = 0;
   flags = 0;
   while ((opt = getopt(argc, argv, "nt:")) != -1) {
       switch (opt) {
       case 'n':
           flags = 1;
           break;
       case 't':
           nsecs = atoi(optarg);
           tfnd = 1;
           break;
       default: /* '?' */
           fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
                   argv[0]);
           exit(EXIT_FAILURE);
       }
   }

   printf("flags=%d; tfnd=%d; nsecs=%d; optind=%d\n",
           flags, tfnd, nsecs, optind);

   if (optind >= argc) {
       fprintf(stderr, "Expected argument after options\n");
       exit(EXIT_FAILURE);
   }

   printf("name argument = %s\n", argv[optind]);

   /* Other code omitted */

   exit(EXIT_SUCCESS);
}
int getopt(int argc, char * const argv[], const char *optstring);

参数:

  • argc和argv与main函数的两个参数一致
  • optstring:表示我们短选项的字符串
    如:“sEbt:hv”。表示程序支持的命令行参数有-s、-E、-b、-t、-h和-v。
    冒号的含义表示:
    • 只有一个字符,不带冒号:表示选项,如-h和-v。
    • 一个字符,后面接一个冒号:表示选项后面带一个参数,如-s、-E、-b和-t
extern char *optarg;
extern int optind, opterr, optopt;

4个全局变量

  • optarg:当前选项对应的参数值。
  • optind:argv中要处理的下一个元素的索引。
  • opterr:默认情况下,opterr有一个非零值,opterr设置为零,那么getopt()不会打印错误消息。
  • optopt:表示错误选项字符。

据此编写出基本的框架

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

extern char *optarg;
extern int optind, opterr, optopt;

int
main(int argc, char *argv[])
{
   int hit_count=0,miss_count=0,eviction_count=0;  //命中,未命中,驱逐
   
   int opt;
   int s=0;      //高速缓存组S=2^s
   int E=0;      //高速缓存行
   int b=0;      //数据块B=2^b
   int tag_v=0;  //是否查看过程信息,tag_v==1查看 tag_v==0查看
   char *file_name;
   FILE *file;
   //提示(帮助)的信息
   char help[]="Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n"
            "Options:\n"
            "-h          Print this help message.\n"
            "-v          Optional verbose flag.\n"
            "-s <num>    Number of set index bits.\n"
            "-E <num>    Number of lines per set.\n"
            "-b <num>    Number of block offset bits.\n"
            "-t <file>   Trace file.\n\n"
            "Examples:\n"
            "linux>     %s  -s 4 -E 1 -b 4 -t traces/yi.trace\n"
            "linux>     %s  -v -s 8 -E 2 -b 4 -t traces/yi.trace\n";

   //"sEbt:hv":-s、-E、-b、-t 可接收字符,-h和-v不x接受
   while ((opt = getopt(argc, argv, "sEbt:hv")) != -1) {
      switch (opt) {
         case 'h':  
            fprintf(stderr, help,argv[0],argv[0],argv[0]);	//argv[0]是命令行参数的第一个值,即运行的可执行文件名
            break;
         case 'v':
            tag_v = 1;
            break;
         case 's':                        
            s=atoi(argv[optind]);   //atoi字符转十进制
            break;
         case 'E':                        
            E=atoi(argv[optind]);
            break;
         case 'b':                       
            b=atoi(argv[optind]);
            break;

         case 't':
            file_name=argv[optind-1];     //获取文件名
            
            file = fopen(file_name,"r");  //打开文件
            if(file == NULL){
               perror(file_name);
               exit(EXIT_FAILURE);
            }
            break;

         default:
            fprintf(stderr, help,argv[0],argv[0],argv[0]);
            exit(EXIT_FAILURE);
      }
   }
   /* Other code omitted */

   printSummary(hit_count,miss_count,eviction_count);
   return 0;
}

用-h命令运行如下(gcc编译要带上cachelab.c):
在这里插入图片描述
(有一些细节没处理好,不过这部分不是主要的)

模拟高速缓存结构

我们要设计一个数据结构,使它能模仿高速缓存结构的行为
在这里插入图片描述

《CS:APP3e》图6-25 高速缓存(S,E,B,m)的通用组织。a)高速缓存是一个高速缓存组。每个组包括一个行或多个行,每个行包含一个有效位,一些标记位,以及一个数据块

可以看出这是一个“组”里面有“行”,行里面有有效位,标记位,以及一个数据块。
可以把有效位,标记位,以及一个数据块定义成一个结构体,然后创建该结构体的二维数组就可以模拟高速缓存组。

如下所示:

struct ROW{
   int validBit;     //有效位
   int tagBit;       //标记位
   char *block;      //数据块
};

/**
 * 设置模拟缓存。初始化
 * S=2^s组,即1<<s。
 * B=2^b字节,即1<<b。    //数据块的大小
 **/

int S=1<<s;
int block_size=1<<b;

struct ROW cacheLine[S][E];

for(int i = 0;i < S;i++){
   for(int j = 0;j < E;j++){
      cacheLine[i][j].validBit=0;    
      cacheLine[i][j].tagBit=-1;
      cacheLine[i][j].block = malloc(block_size);
   }
}

解析地址

我们要用程序来解析地址。
在这里插入图片描述

《CS:APP3e》图6-25 b)高速缓存结构将m个地址位划分成了t个标记位、s个组索引位和b个块偏移位

要创建两个掩码来取到块偏移位和组索引位

  • 创建一个b位全是1其他位全是0的掩码。
  • 创建一个s位全是1其他为全是0的掩码。

这里

  • 一个b位全是1其他位全是0的掩码:(2b)-1
  • 一个s位全是1其他为全是0的掩码:((2s)-1)<<b
#include <math.h>

int mask_b = (int)pow(2,b)-1;           //创建块偏移b位的掩码
int mask_s = ((int)pow(2,s)-1)<<b;      //创建组索引s位的掩码

比如说有个地址addres,这样就可以计算组索引和标记位

int cacheLine_index = (addres & mask_s)>>b;  //组索引
int tag = addres>>s>>b;                      //标记位

解析文件

实验给的.trace是这样子的
每行表示一个或两个内存访问。每一行的格式为
[space] operation address,size
operation字段表示内存访问的类型:“I” 表示指令加载,“L” 表示数据加载,“S” 表示数据存储,“M” 表示数据修改(即,数据加载后跟着一个数据存储)。在每个 “I” 之前从来没有空格。每个 “M”、“L” 和 “S” 前面总是有一个空格。address字段指定一个64位的十六进制内存地址。size字段指定操作访问的字节数。

 L 0,1
 L 1,1
 L 2,1
 L 3,1
 S 4,1
 L 5,1
 S 6,1
 L 7,1
 S 8,1
 L 9,1
 S a,1
 L b,1
 S c,1
 L d,1
 S e,1
 M f,1

有"I"就是这样子的

 S 00600aa0,1
I  004005b6,5
I  004005bb,5
I  004005c0,5

用char *fgets(char *str, int n, FILE *stream);一行一行读取并进行处理。
用char *strtok(char *str, const char *delim);分割字符串。
要处理address,因为它是字符的表示形式要转成整数才可以对它的位进行处理。
写个htoi方法:十六进制字符转成十进制整数。

int mask_b = (int)pow(2,b)-1;           //创建块偏移b位的掩码
int mask_s = ((int)pow(2,s)-1)<<b;      //创建组索引s位的掩码

char ch[16];                     //刚好可以容纳一行数据的大小

const char Separator[3]=" ,";    //分隔符
char *token;                     //子串

while(fgets(ch,16,file)!=NULL){
   token = strtok(ch,Separator);
   char *instruction = token;       //内存访问的类型
   if (*instruction == 'I')         //I类型不用处理
   {
      continue;
   }
   token = strtok(NULL,Separator);
   char *addres_char = token;       //地址的字符表示
   long addres = htoi(addres_char); //地址的十进制表示
   
   token = strtok(NULL,Separator);
   int size = atoi(token);  

   int cacheLine_index = (addres & mask_s)>>b;  //组索引
   int tag = addres>>s>>b;                     //标记位        

   if(tag_v) printf("%s %s,%d \n",instruction,addres_char,size);
   
}

/**
 * 十六进制字符转成十进制整数
 **/
long htoi(char *s){
   long n = 0;
   for(int i = 0;(s[i] >= '0' && s[i] <= '9') || (s[i]>= 'a' && s[i] <= 'f');i++)
   {
      if(s[i] > '9')
      {
         n = 16 * n +(10 + s[i] - 'a');
      }
      else
      {
         n = 16 * n + (s[i] - '0');
      }
   }

   return n;
}

运行结果(用到了math.h中声明的库函数pow,gcc编译要加"-lm"):
在这里插入图片描述

模拟缓存行为(当E=1时,没替换策略的简易版)

先解决当E=1时。
这个就简单了,只要获取地址中的组索引
按以下步骤,只要其中一个步骤完成即可

  1. 遍历行是否有效,在判断标记位是否相同,标记位相同就命中。
  2. 1没有命中。遍历行是否有无效(空)的缓存区,有就写入。
  3. 2也没有。驱逐一个缓存区,写入
  • "I"表示指令加载:不用处理。
  • "L"表示数据加载和"S"表示数据存储:一样处理
  • "M"数据修改(即,数据加载后跟着一个数据存储):“L"然后"S”
int tag_M = *instruction == 'M' ? 2:1;          //'M' 循环两遍,'S''I'一遍

do{
   //1.遍历行是否有效,在判断标记位是否相同,标记位相同就命中。
   for(int i = 0;i < E;i++){                    //遍历一组中的所有行
      struct ROW *one_row = &cacheLine[cacheLine_index][i]; //获取一行
      if(one_row->validBit && one_row->tagBit == tag){     //有效且标记相同则命中
            hit_count++;                        //命中

            if(tag_v) printf("hit ");
            goto end;
      }
   }

   //2.遍历行是否有无效(空)的缓存区,有就写入。
   for(int i =0;i < E;i++){      //无效:未命中,要写入无效的缓存区
      struct ROW *one_row = &cacheLine[cacheLine_index][i];
      if(!one_row->validBit){    
         miss_count++;     
         one_row->validBit = 1;
         one_row->tagBit = tag;
         if(tag_v)printf("miss ");
         goto end;
      }
   }

   //3.驱逐一个缓存区,写入
   //驱逐使用LRU(最近使用最少的)替换策略,现在还没写,因为E=1的情况用不用都一样
   for (int i = 0; i < E; i++){  //缓存区满了,要驱逐
      struct ROW *one_row = &cacheLine[cacheLine_index][i];
      miss_count++;        //未命中
      eviction_count++;    //驱逐
      one_row->validBit = 1;
      one_row->tagBit = tag;

      if(tag_v)printf("miss ");
      goto end;
   }
end:
   if (tag_v && !(tag_M-1)){printf("\n");}            //不是'M'就要换行
   tag_M--;
}while(tag_M);
代码

截止目前为止的代码合起来为

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

extern char *optarg;
extern int optind, opterr, optopt;

long htoi(char *s);  //十六进制字符转成十进制整数

struct ROW{
   int validBit;     //有效位
   int tagBit;       //标记位
   char *block;      //数据块
};

int
main(int argc, char *argv[])
{
   int hit_count=0,miss_count=0,eviction_count=0;  //命中,未命中,驱逐
   
   int opt;
   int s=0;      //高速缓存组S=2^s
   int E=0;      //高速缓存行
   int b=0;      //数据块B=2^b
   int tag_v=0;  //是否查看过程信息,tag_v==1查看 tag_v==0查看
   char *file_name;
   FILE *file;
   //提示(帮助)的信息
   char help[]="Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n"
            "Options:\n"
            "-h          Print this help message.\n"
            "-v          Optional verbose flag.\n"
            "-s <num>    Number of set index bits.\n"
            "-E <num>    Number of lines per set.\n"
            "-b <num>    Number of block offset bits.\n"
            "-t <file>   Trace file.\n\n"
            "Examples:\n"
            "linux>     %s  -s 4 -E 1 -b 4 -t traces/yi.trace\n"
            "linux>     %s  -v -s 8 -E 2 -b 4 -t traces/yi.trace\n";

   //"sEbt:hv":-s、-E、-b、-t 可接收字符,-h和-v不接收
   while ((opt = getopt(argc, argv, "sEbt:hv")) != -1) {
      switch (opt) {
         case 'h':  
            fprintf(stderr, help,argv[0],argv[0],argv[0]);  //argv[0]是命令行参数的第一个值,即运行的可执行文件名
            break;
         case 'v':
            tag_v = 1;
            break;
         case 's':                        
            s=atoi(argv[optind]);   //atoi字符转十进制
            break;
         case 'E':                        
            E=atoi(argv[optind]);
            break;

         case 'b':                       
            b=atoi(argv[optind]);
            break;

         case 't':
            file_name=argv[optind-1];     //获取文件名
            
            file = fopen(file_name,"r");  //打开文件
            if(file == NULL){
               perror(file_name);
               exit(EXIT_FAILURE);
            }
            break;

         default:
            fprintf(stderr, help,argv[0],argv[0],argv[0]);
            exit(EXIT_FAILURE);
      }
   }

   /**
    * 设置模拟缓存。初始化
    * S=2^s组,即1<<s。
    * B=2^b字节,即1<<b。
    **/

   int S=1<<s;
   int block_size=1<<b;    //数据块的大小

   struct ROW cacheLine[S][E];

   for(int i = 0;i < S;i++){
      for(int j = 0;j < E;j++){
         cacheLine[i][j].validBit=0;    
         cacheLine[i][j].tagBit=-1;
         cacheLine[i][j].block = malloc(block_size);
      }
   }

   int mask_b = (int)pow(2,b)-1;           //创建块偏移b位的掩码
   int mask_s = ((int)pow(2,s)-1)<<b;      //创建组索引s位的掩码

   char ch[16];                     //刚好可以容纳一行数据的大小

   const char Separator[3]=" ,";    //分隔符
   char *token;                     //子串
   
   while(fgets(ch,16,file)!=NULL){
      token = strtok(ch,Separator);
      char *instruction = token;       //内存访问的类型
      if (*instruction == 'I')         //I类型不用处理
      {
         continue;
      }
      token = strtok(NULL,Separator);
      char *addres_char = token;       //地址的字符表示
      long addres = htoi(addres_char); //地址的十进制表示
      

      token = strtok(NULL,Separator);
      int size = atoi(token);  

      int cacheLine_index = (addres & mask_s)>>b;  //组索引
      int tag = addres>>s>>b;                     //标记位        

      if(tag_v) printf("%s %s,%d ",instruction,addres_char,size);

      int tag_M = *instruction == 'M' ? 2:1;          //'M' 循环两遍,'S''I'一遍

      do{
         //1.遍历行是否有效,在判断标记位是否相同,标记位相同就命中。
         for(int i = 0;i < E;i++){                    //遍历一组中的所有行
            struct ROW *one_row = &cacheLine[cacheLine_index][i]; //获取一行
            if(one_row->validBit && one_row->tagBit == tag){     //有效且标记相同则命中
                  hit_count++;                        //命中

                  if(tag_v) printf("hit ");
                  goto end;
            }
         }

         //2.遍历行是否有无效(空)的缓存区,有就写入。
         for(int i =0;i < E;i++){      //无效:未命中,要写入无效的缓存区
            struct ROW *one_row = &cacheLine[cacheLine_index][i];
            if(!one_row->validBit){    
               miss_count++;     
               one_row->validBit = 1;
               one_row->tagBit = tag;
               if(tag_v)printf("miss ");
               goto end;
            }
         }

         //3.驱逐一个缓存区,写入
         //驱逐使用LRU(最近使用最少的)替换策略,现在还没写,因为E=1的情况用不用都一样
         for (int i = 0; i < E; i++){  //缓存区满了,要驱逐
            struct ROW *one_row = &cacheLine[cacheLine_index][i];
            miss_count++;        //未命中
            eviction_count++;    //驱逐
            one_row->validBit = 1;
            one_row->tagBit = tag;

            if(tag_v)printf("miss ");
            goto end;
         }
      end:
         if (tag_v && !(tag_M-1)){printf("\n");}            //不是'M'就要换行
         tag_M--;
      }while(tag_M);
      
   }

   //释放分配的空间
   for(int i = 0;i < S;i++){
      for(int j = 0;j < E;j++){
         free(cacheLine[i][j].block);
      }
   }

   printSummary(hit_count,miss_count,eviction_count);
   return 0;
}


/**
 * 十六进制字符转成十进制整数
 **/
long htoi(char *s){
   long n = 0;
   for(int i = 0;(s[i] >= '0' && s[i] <= '9') || (s[i]>= 'a' && s[i] <= 'f');i++)
   {
      if(s[i] > '9')
      {
         n = 16 * n +(10 + s[i] - 'a');
      }
      else
      {
         n = 16 * n + (s[i] - '0');
      }
   }

   return n;
}

这段代码只能解决E=1时。
根据文档用测试程序来打分。

linux> make

在目录下make会发现报错了,挺奇怪的,官方文档中建议用getopt,编译的环境又有问题。
在这里插入图片描述
找到目录下的Makefile文件打开注释掉这段话在这里插入图片描述
重新make就可以了
在这里插入图片描述
运行评分系统,会发现所有E=1的都正确了(yi.trace的E=2也正确了,是碰巧)。
在这里插入图片描述
现在已经21分了,还差6分。还剩下两个不正确,要实现LRU(最近使用最少的)替换策略。

设计模拟LRU高速缓存结构

可以用一个双链表来实现LRU替换策略。
链头是最近访问的Cache。
链尾是最后访问的缓存区。
当一个位置被命中之后,通过调整链表的指向,将该位置调整到链表头的位置,新加入的Cache直接加到链表头中。
这样,在多次进行Cache操作后,最近被命中的,就会被向链表头方向移动,而没有命中的,会向链表后面移动,链表尾则表示最近最少使用的Cache。

发现在上面的

struct ROW{
   int validBit;     //有效位
   int tagBit;       //标记位
   char *block;      //数据块
};

中的char *block是用不上的,只要标记位命中就可以了。

重新设计一个数据结构,每一组加多一个双链表。
请添加图片描述
数据结构如下:

struct ROW{
   int validBit;     //有效位
   int tagBit;       //标记位
};

struct LRU_ROW{
   struct line *head;	//链表头
   struct ROW *E_ROW;	//组
};

struct line{
   struct line *prior;  //指向直接前趋
   struct line *next;   //指向直接后继
   int tagBit;          //标记
};

/**
 * 设置模拟缓存。初始化
 * S=2^s组,即1<<s。
 * B=2^b字节,即1<<b。
 **/

int S=1<<s;
int block_size=1<<b;    //数据块的大小

struct LRU_ROW cacheLine[S];

for(int i = 0;i < S;i++){
   cacheLine[i].head=(struct line*)malloc(sizeof(struct line));
   cacheLine[i].head->tagBit=-1;
   cacheLine[i].E_ROW=malloc(sizeof(struct ROW)*E);
   for(int j = 0;j < E;j++){
      cacheLine[i].E_ROW[j].validBit=0;    
      cacheLine[i].E_ROW[j].tagBit=-1;
   }
}

剩下的模拟操作就是对链表进行操作,要注意一些特殊情况,如头和尾。

int tag_M = *instruction == 'M' ? 2:1;          //'M' 循环两遍,'S''I'一遍
do{
   struct LRU_ROW *cache_lru_row = &cacheLine[cacheLine_index];   //获取该组
   struct line *body = cache_lru_row->head;     //获取该组的双链表头
   for(int i = 0;i < E;i++){                    //遍历一组中的所有行

      struct ROW *one_row = &cache_lru_row->E_ROW[i]; //获取一行
      if(one_row->validBit && one_row->tagBit == tag){     //有效且标记相同则命中
            
            //找到双链表中命中的缓存,准备放到链表头
            while(body!=NULL){
               if(body->tagBit == tag){
                  break;
               }
               body=body->next;
            }

            //放到链表头,要判断,是不是本身就是头,是头就跳过
            if (body!=cache_lru_row->head)
            {
               body->prior->next = body->next;

               //判断是不是链表尾,是尾就跳过
               if(body->next != NULL){
                  body->next->prior = body->prior;
               }

               body->prior == NULL;
               body->next = cache_lru_row->head;
               cache_lru_row->head->prior=body;
               cache_lru_row->head = body;
            }
            hit_count++;                        //命中
            if(tag_v) printf("hit ");
            goto end;
      }
   }

   for(int i = 0;i < E;i++){      //无效:未命中,要写入无效的缓存区
      
      struct ROW *one_row = &cache_lru_row->E_ROW[i]; //获取一行
      if(!(one_row->validBit)){ 

         //准备放入链表的新结点
         struct line * temp=(struct line*)malloc(sizeof(struct line));
         temp->prior=NULL;
         temp->next=NULL;
         temp->tagBit = tag;  

         //判断是不是链表中的第一个结点,是就跳过
         if(body->tagBit!=-1){
            temp->next = body;
            body->prior = temp;
         }
         //设置尾头结点
         cache_lru_row->head = temp;

         miss_count++;     
         one_row->validBit = 1;
         one_row->tagBit = tag;

         if(tag_v)printf("miss ");
         goto end;
      }
   }
   /**
    * 又没命中,又没有多余的缓存区,则驱逐
    * 
    * 驱逐的是双链表的最后一个
    * 驱逐就是把双链表最后一个移到开头并重新设置tag
    **/
   while(body->next!=NULL){
      body = body->next;
   }
   //判断链表中是不是只有一个结点
   if (body!=cache_lru_row->head)
   {
      body->prior->next=NULL;
      body->next=cache_lru_row->head;
      cache_lru_row->head->prior=body;
   }
   cache_lru_row->head=body;


   //找到在缓存中对应与双链表最后一个的缓存,移除,写入新的缓存。
   struct ROW *one_row;
   for(int j = 0;j < E;j++){
      one_row=&cacheLine[cacheLine_index].E_ROW[j];
      if(body->tagBit == (one_row->tagBit)){
         break;
      }
   }
   body->tagBit=tag;

   one_row->validBit=1;
   one_row->tagBit=tag;

   if(tag_v)printf("miss eviction");
   miss_count++;        //未命中
   eviction_count++;    //驱逐
end:
   if (tag_v && !(tag_M-1)){printf("\n");}            //不是'M'就要换行
   tag_M--;
}while(tag_M);
代码(满分)

完整代码如下

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

extern char *optarg;
extern int optind, opterr, optopt;

long htoi(char *s);  //十六进制字符转成十进制整数

struct ROW{
   int validBit;     //有效位
   int tagBit;       //标记位
};

struct LRU_ROW{
   struct line *head;   //链表头
   struct ROW *E_ROW;   //组
};

struct line{
   struct line *prior;  //指向直接前趋
   struct line *next;   //指向直接后继
   int tagBit;          //标记
};


int
main(int argc, char *argv[])
{
   int hit_count=0,miss_count=0,eviction_count=0;  //命中,未命中,驱逐
   
   int opt;
   int s=0;      //高速缓存组S=2^s
   int E=0;      //高速缓存行
   int b=0;      //数据块B=2^b
   int tag_v=0;  //是否查看过程信息
   char *file_name;
   FILE *file;
   //提示(帮助)的信息
   char help[]="Usage: %s [-hv] -s <num> -E <num> -b <num> -t <file>\n"
            "Options:\n"
            "-h          Print this help message.\n"
            "-v          Optional verbose flag.\n"
            "-s <num>    Number of set index bits.\n"
            "-E <num>    Number of lines per set.\n"
            "-b <num>    Number of block offset bits.\n"
            "-t <file>   Trace file.\n\n"
            "Examples:\n"
            "linux>     %s  -s 4 -E 1 -b 4 -t traces/yi.trace\n"
            "linux>     %s  -v -s 8 -E 2 -b 4 -t traces/yi.trace\n";

   //"sEbt:hv":-s、-E、-b、-t 可接收字符,-h和-v不接收
   while ((opt = getopt(argc, argv, "sEbt:hv")) != -1) {
      switch (opt) {
         case 'h':  
            fprintf(stderr, help,argv[0],argv[0],argv[0]);  //argv[0]是命令行参数的第一个值,即运行的可执行文件名
            break;
         case 'v':
            tag_v = 1;
            break;
         case 's':                        
            s=atoi(argv[optind]);   //atoi字符转十进制
            break;
         case 'E':                        
            E=atoi(argv[optind]);
            break;

         case 'b':                       
            b=atoi(argv[optind]);
            break;

         case 't':
            file_name=argv[optind-1];     //获取文件名
            
            file = fopen(file_name,"r");  //打开文件
            if(file == NULL){
               perror(file_name);
               exit(EXIT_FAILURE);
            }
            break;

         default:
            fprintf(stderr, help,argv[0],argv[0],argv[0]);
            exit(EXIT_FAILURE);
      }
   }

   /**
    * 设置模拟缓存。初始化
    * S=2^s组,即1<<s。
    * B=2^b字节,即1<<b。
    **/

   int S=1<<s;
   int block_size=1<<b;    //数据块的大小

   struct LRU_ROW cacheLine[S];
   for(int i = 0;i < S;i++){
      cacheLine[i].head=(struct line*)malloc(sizeof(struct line));
      cacheLine[i].head->tagBit=-1;
      cacheLine[i].E_ROW=malloc(sizeof(struct ROW)*E);
      for(int j = 0;j < E;j++){
         cacheLine[i].E_ROW[j].validBit=0;    
         cacheLine[i].E_ROW[j].tagBit=-1;
      }
   }

   int mask_b = (int)pow(2,b)-1;           //创建块偏移b位的掩码
   int mask_s = ((int)pow(2,s)-1)<<b;      //创建组索引s位的掩码

   char ch[16];                     //刚好可以容纳一行数据的大小

   const char Separator[3]=" ,";    //分隔符
   char *token;                     //子串
   
   while(fgets(ch,16,file)!=NULL){

      token = strtok(ch,Separator);
      char *instruction = token;       //内存访问的类型
      if (*instruction == 'I')         //I类型不用处理
      {
         continue;
      }
      token = strtok(NULL,Separator);
      char *addres_char = token;       //地址的字符表示
      long addres = htoi(addres_char); //地址的十进制表示
      

      token = strtok(NULL,Separator);
      int size = atoi(token);  

      int cacheLine_index = (addres & mask_s)>>b;  //组索引
      int tag = addres>>s>>b;                     //标记位  

      if(tag_v) printf("%s %s,%d ",instruction,addres_char,size);

      int tag_M = *instruction == 'M' ? 2:1;          //'M' 循环两遍,'S''I'一遍
      do{
         struct LRU_ROW *cache_lru_row = &cacheLine[cacheLine_index];   //获取该组
         struct line *body = cache_lru_row->head;     //获取该组的双链表头
         for(int i = 0;i < E;i++){                    //遍历一组中的所有行

            struct ROW *one_row = &cache_lru_row->E_ROW[i]; //获取一行
            if(one_row->validBit && one_row->tagBit == tag){     //有效且标记相同则命中
                  

                  //找到双链表中命中的缓存,准备放到链表头
                  while(body!=NULL){
                     if(body->tagBit == tag){
                        break;
                     }
                     body=body->next;
                  }

                  //放到链表头,要判断,是不是本身就是头,是头就跳过
                  if (body!=cache_lru_row->head)
                  {
                     body->prior->next = body->next;

                     //判断是不是链表尾,是尾就跳过
                     if(body->next != NULL){
                        body->next->prior = body->prior;
                     }

                     body->prior == NULL;
                     body->next = cache_lru_row->head;
                     cache_lru_row->head->prior=body;
                     cache_lru_row->head = body;
                  }
                  hit_count++;                        //命中
                  if(tag_v) printf("hit ");
                  goto end;
            }
         }

         for(int i = 0;i < E;i++){      //无效:未命中,要写入无效的缓存区
            
            struct ROW *one_row = &cache_lru_row->E_ROW[i]; //获取一行
            if(!(one_row->validBit)){ 

               //准备放入链表的新结点
               struct line * temp=(struct line*)malloc(sizeof(struct line));
               temp->prior=NULL;
               temp->next=NULL;
               temp->tagBit = tag;  

               //判断是不是链表中的第一个结点,是就跳过
               if(body->tagBit!=-1){
                  temp->next = body;
                  body->prior = temp;
               }
               //设置尾头结点
               cache_lru_row->head = temp;

               miss_count++;     
               one_row->validBit = 1;
               one_row->tagBit = tag;

               if(tag_v)printf("miss ");
               goto end;
            }
         }
         /**
          * 又没命中,又没有多余的缓存区,则驱逐
          * 
          * 驱逐的是双链表的最后一个
          * 驱逐就是把双链表最后一个移到开头并重新设置tag
          **/
         while(body->next!=NULL){
            body = body->next;
         }
         //判断链表中是不是只有一个结点,是就跳过
         if (body!=cache_lru_row->head)
         {
            body->prior->next=NULL;
            body->next=cache_lru_row->head;
            cache_lru_row->head->prior=body;
         }
         cache_lru_row->head=body;


         //找到在缓存中对应与双链表最后一个的缓存,移除,写入新的缓存。
         struct ROW *one_row;
         for(int j = 0;j < E;j++){
            one_row=&cacheLine[cacheLine_index].E_ROW[j];
            if(body->tagBit == (one_row->tagBit)){
               break;
            }
         }
         body->tagBit=tag;

         one_row->validBit=1;
         one_row->tagBit=tag;

         if(tag_v)printf("miss eviction");
         miss_count++;        //未命中
         eviction_count++;    //驱逐
      end:
         if (tag_v && !(tag_M-1)){printf("\n");}            //不是'M'就要换行
         tag_M--;
      }while(tag_M);
      
   }

   //释放分配的空间
   for (int i = 0; i < S; i++)
   {
      free(cacheLine[i].head);
      free(cacheLine[i].E_ROW);
   }

   printSummary(hit_count,miss_count,eviction_count);
   return 0;
}


/**
 * 十六进制字符转成十进制整数
 **/
long htoi(char *s){
   long n = 0;
   for(int i = 0;(s[i] >= '0' && s[i] <= '9') || (s[i]>= 'a' && s[i] <= 'f');i++)
   {
      if(s[i] > '9')
      {
         n = 16 * n +(10 + s[i] - 'a');
      }
      else
      {
         n = 16 * n + (s[i] - '0');
      }
   }

   return n;
}

测试一下
可以看到拿到了27分满分。
在这里插入图片描述

B部分

运行一下熟悉环境
在这里插入图片描述
结果运行异常,说是找不到valgrind
需要安装valgrind

linux> sudo apt-get install valgrind

然后在运行就可以了(把trans中的代码复制了一份到transpose_submit)
在这里插入图片描述

32*32

给了一个最简单的版本

void trans(int M, int N, int A[N][M], int B[M][N])
{
    int i, j, tmp;

    for (i = 0; i < N; i++) {
        for (j = 0; j < M; j++) {
            tmp = A[i][j];
            B[j][i] = tmp;
        }
    }    

}

这很明显,啥都没用上。
s=5,E=1,b=5。S=25=32,每组一行,B=25=32。
cache有32组,每组一行,每行可以储存32字节的数据,int类型为4字节,所以每个缓存中的每个数据块可以保存8个int元素。
可以用循环展开,一次循环复制8个。
A矩阵中,这8个,第1个未命中,然后放8个进缓存区,剩下7个都能命中。
而B矩阵呢?它的步长为N,所以在第一次把A矩阵复制到B矩阵时,B是全部未命中,然后缓存中就多了
B[0][0]~B[0][7]
B[1][0]~B[1][7]
B[2][0]~B[2][7]
B[3][0]~B[3][7]
B[4][0]~B[4][7]

这样下8次都可以命中(外循环间隔是8),而每一次都只有因为A数组而被逐出造成的一个未命中。
所以把它分为8*8的块。

代码如下

char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
    int i, j, temp0,temp1,temp2,temp3,temp4,temp5,temp6,temp7;

    if(M==32){
        for (j = 0; j < 32; j=j+8) {
            for (i = 0; i < 32;i++) {
                temp0 = A[i][j];
                temp1 = A[i][j+1];
                temp2 = A[i][j+2];
                temp3 = A[i][j+3];
                temp4 = A[i][j+4];
                temp5 = A[i][j+5];
                temp6 = A[i][j+6];
                temp7 = A[i][j+7];

                B[j][i] = temp0;
                B[j+1][i]=temp1;
                B[j+2][i]=temp2;
                B[j+3][i]=temp3;
                B[j+4][i]=temp4;
                B[j+5][i]=temp5;
                B[j+6][i]=temp6;
                B[j+7][i]=temp7;

            }
        }    
    }
    
}

make然后运行
在这里插入图片描述
<300。8分。
在这里插入图片描述

根据trace.f0给的起始地址,
A是30b080
B是34b080
在这里插入图片描述
写了个程序看了一下A和B的组数
在这里插入图片描述

如下所示

A/B[][0]~A/B[][7]A/B[0][8]~A/B[][15]A/B[][16]~A/B[][23]A/B[][24]~A/B[31]
4567
891011
12131415
16171819
20212223
24252627
28293031
0123
4567
891011
12131415
16171819
20212223
24252627
28293031
0123
4567
891011
12131415
16171819
20212223
24252627
28293031
0123
4567
891011
12131415
16171819
20212223
24252627
28293031
0123

A数组中的元素命中情况
"M"未命中,"H"命中。

012345678910111213141516171819202122232425262728293031
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH

B数组中的元素命中情况
"M"未命中,"H"命中。

012345678910111213141516171819202122232425262728293031
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MMHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHMHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHMHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHMHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHMHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHMHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHMMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMMHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHMHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHMHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHMHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHMHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHMHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHMMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMMHHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHMHHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHMHHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHMHHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHMHHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHMHMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHMMHHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMMHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMMHHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHMHHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHMHHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHMHHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHMHH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHMH
MHHHHHHHMHHHHHHHMHHHHHHHMHHHHHHM

64*64

if(M==64 && N==64){
        for (j = 0; j < 64; j=j+8) {
            for (i = 0; i < 64;i=i+8) {
                for(x = j;x < j + 4;x++){
                    temp0 = A[x][i];temp1 = A[x][i+1];temp2 = A[x][i+2];temp3 = A[x][i+3];
                    temp4 = A[x][i+4];temp5 = A[x][i+5];temp6 = A[x][i+6];temp7 = A[x][i+7];

                    B[i][x] = temp0;B[i+1][x] = temp1;B[i+2][x] = temp2;B[i+3][x] = temp3;
                    B[i][x+4] = temp4;B[i+1][x+4] = temp5;B[i+2][x+4] = temp6;B[i+3][x+4] = temp7;
                }
                for(y = i;y < i + 4;y++){
                    temp0 = A[j+4][y];temp1 = A[j+5][y];temp2 = A[j+6][y];temp3 = A[j+7][y];
                    temp4 = B[y][j+4];temp5 = B[y][j+5];temp6 = B[y][j+6];temp7 = B[y][j+7];

                    B[y][j+4] = temp0;B[y][j+5] = temp1;B[y][j+6] = temp2;B[y][j+7]=temp3;
                    B[y+4][j] = temp4;B[y+4][j+1] = temp5;B[y+4][j+2] = temp6;B[y+4][j+3]=temp7;
                }
                for(x = j + 4;x < j + 8;x++){
                    temp0 = A[x][i+4];temp1 = A[x][i+5];temp2 = A[x][i+6];temp3 = A[x][i+7];
                    B[i+4][x] = temp0;B[i+5][x] = temp1;B[i+6][x] = temp2;B[i+7][x] = temp3;
                }
            }
        }    
    }

结果
在这里插入图片描述

61*67

用16*16分块

if(M==61 && N==67){
        for(i = 0;i < N;i+=16){
            for(j = 0;j < M;j+=16){
                for(x = i;x < N && x < i+16;x++){
                    for(y=j;y < M && y < j+16;y++){
                        B[y][x]=A[x][y];
                    }
                }
            }
        }
    }

结果
在这里插入图片描述

总测

在这里插入图片描述

  • 16
    点赞
  • 96
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值