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

2023-11-10

前言

《深入理解计算机系统》实验五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]
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31
0 1 2 3

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

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H

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

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M M H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H M H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H M H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H M H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H M H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H M H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H M M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M M H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H M H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H M H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H M H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H M H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H M H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H M M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M M H H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H M H H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H M H H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H M H H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H M H H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H M H M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H M M H H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M M H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M M H H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H M H H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H M H H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H M H H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H M H H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H M H
M H H H H H H H M H H H H H H H M H H H H H H H M H H H H H H M

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];
                    }
                }
            }
        }
    }

结果
在这里插入图片描述

总测

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《深入理解计算机系统》实验五Cache Lab 的相关文章

  • fork之后子进程到底复制了父进程什么

    fork之后子进程到底复制了父进程什么 发表于2015 4 3 9 54 08 2161人阅读 分类 操作系统 include
  • 6.OS运行机制(补充)

    中断
  • 计算机网路基础 - 一些基本概念与网络结构

    1 基本概念 计算机网络 通信技术 计算机技术 是两项技术紧密结合的产物 通信系统的基础模型 计算机网络 是指将地理位置不同 具有独立功能的多台计算机及其外部设备 通过通信线路连接 在网络操作系统 网络管理软件及网络通信协议的管理和协调下
  • Linux系统的安装(在VM虚拟机上安装CentOS 7)

    工具准备 物理计算机一台 配置要求 操作系统 win10 64位 大家基本上都是 硬盘可用容量 20G以上 内存容量 4G以上 虚拟机安装包 VMware workstation full 12 5 下载链接 点我下载 提取码 9gha C
  • CentOS 7 关闭网络限制

    1 安装CentOS 7 3操作系统mini版本即可 2 设置关闭Selinux 编辑 etc selinux config vi etc selinux config SELINUX disabled 重启机器 查看selinux状态 s
  • Linux网络安全-Zabbix入门(一)

    一 基本概念 1 监控目的 运行情况 提前发现问题 2 监控资源类别 公开 tcp udp 端口 私有 cpu 磁盘 监控一切需要监控的东西 只要能够想到 能够用命令实现的都能用来监控 如果想远程管理服务器就有远程管理卡 比如Dell id
  • pycharm内存不足时如何修改设置?

    Help gt Find Action gt type VM Options gt Click Edit Custom VM Options Pycharm 2016 2 will open the appropriate vmoption
  • Ubuntu 10.10下安装TFTP的步骤 tftp-hpa版本

    背景 由于想要在tq2440板子上用tftp下载kernel 所以要在自己的PC机的Ubuntu 10 10上安装tftp服务 所以就去网上找了些教程 但是很悲剧 按照那些教程去操作 结果还都是无法正常运行tftp服务 最后还是从一个外国人
  • 《一个操作系统的实现》读书笔记-- 第一章--最小的“操作系统”

    一 最简单的 操作系统 最最简单的 操作系统 就是一个最最简单的引导扇区 Boot Sector 虽然它不具有任何功能 但是它却能够直接在裸机上运行 不依赖其他软件 一个引导扇区是512个字节 并且以0xAA55为结束标识的扇区 下面就是那
  • 程序员的自我修养——链接、装载与库

    1 温故而知新 操作系统概念 北桥 连接高速芯片 系统调用接口 以软件中断的方式提供 如Linux使用0x80号中断作为系统调用接口 多任务系统 进程隔离 设备驱动 直接使用物理内存的弊端 地址空间不隔离 内存使用效率低 程序运行的地址不确
  • 03LinuxC线程学习之线程共享和非共享

    1 线程共享和非共享 1 1 线程共享资源 1 文件描述符表 由于线程间共享进程间的内容 而文件描述符表在主线程的PCB当中 各个线程可以直接去请求访问 所以线程间通信就不需要像进程那样通过管道这些方式通信 2 每种信号的处理方式 即当某个
  • Linux学习--CentOS7.5

    CentOS7命令大全 Linux系统简介 Unix Linux发展史 Linux目录结构 树形结构 查看 切换以及创建目录 文本内容操作 grep工具 关机和重启 Linux命令 基本用法 ls list 使用通配符 mkdir 别名 g
  • 通过源码包*.src.rpm定制开发rpm

    为什么80 的码农都做不了架构师 gt gt gt 1 基本流程 1 下载 安装相应的src rpm包 wget xxx src rpm rpm ivh xxx src rpm 这里的 安装 是指把xxx src rpm中的tar gz p
  • [架构之路-185]-《软考-系统分析师》-3-操作系统基本原理 - 文件索引表

    目录 一 文件的索引块 二 索引分配表 三 索引表的链接方案 四 多层索引 五 混合索引分配 一 文件的索引块 存放在目录中的文件 并非是文件的真实内容 目录中记录了文件的索引块是几号磁盘块 文件对应的索引表是存放在指定的磁盘块中的 二 索
  • 自己动手写操作系统(一)

    本系列文章将一步步实现一个简单的操作系统 实验环境是在Linux系统下通过Bochs虚拟机运行我们自己写的操作系统 一 实验环境搭建 1 Ubuntu的安装 Windows用户可以选择在虚拟机中安装Ubuntu 具体安装教程可自行搜索 2
  • Ubuntu9.04太多乱码(中文不能正常显示)

    最近在使用Ubuntu9 04的过程中 发现有好多地方都出现乱码 其实是中文不能正常显示 现在把我所遇到的所有乱码问题集中一下 方便以后查阅参考 一 Flash乱码 在终端输入 sudo gedit etc fonts conf d 49
  • 《深入理解计算机系统》实验四Architecture Lab

    前言 深入理解计算机系统 实验四Architecture Lab下载和官方文档机翻请看 深入理解计算机系统 实验四Architecture Lab下载和官方文档机翻 我觉得这个文档对整个实验很有帮助 如果你的Y86 64环境还没安装好可以看
  • linux 使用systemctl 启动服务报错: Error: No space left on device

    By default Linux only allocates 8192 watches for inotify which is ridiculously low And when it runs out the error is als
  • I/O设备模型

    I O设备模型 绝大部分的嵌入式系统都包括一些I O Input Outut 输入 输出 设备 例如仪器上的数据显示屏 工业设备上的串口通信 数据采集设备上用于保存数据的Flash或SD卡 以及网络设备的以太网接口等 I O设备模型框架 R
  • 【操作系统xv6】学习记录4-一级页表与二级页表

    占位

随机推荐

  • Biggest Number深搜

    Description You have a maze with obstacles and non zero digits in it You can start from any square walk in the maze and
  • 【设计模式】装饰器模式

    装饰器模式 Decorator Pattern 允许向一个现有的对象添加新的功能 同时又不改变其结构 这种类型的设计模式属于结构型模式 它是作为现有的类的一个包装 装饰器模式通过将对象包装在装饰器类中 以便动态地修改其行为 这种模式创建了一
  • Ogre:Hardwarebuffer

    Ogre Hardwarebuffer 分类 OGRE 2012 07 03 15 56 1097人阅读 评论 0 收藏 举报 buffer float byte 存储 图形 upload Ogre中的硬件缓存是指在显卡上的存储 这和在内存
  • matlab小问题

    编了一个MATLAB小程序 遇到这样的问题 首先代码如下 filename strcat 00 int2str i jpg n i 1 filename 问题为 Subscripted assignment dimension mismat
  • 大数据技术之 Maxwell(1.29.2版本)(最新最全教程)

    第 1 章 Maxwell 概述 1 1 Maxwell 定义 Maxwell 是由美国 Zendesk 开源 用 Java 编写的 MySQL 实时抓取软件 实时读取MySQL 二进制日志 Binlog 并生成 JSON格式的消息 作为生
  • DNN

    文章目录 前向传播 从感知机到神经网络 DNN的基本结构 DNN前向传播算法数学原理 DNN前向传播算法 反向传播算法 BP DNN反向传播算法要解决的问题 DNN反向传播算法的基本思路 DNN反向传播算法过程 损失函数和激活函数的选择 均
  • VSCode使用embed

    VSCode使用embed在html文件中加载网络视频和图片 最近研究html 好奇如何把图片和视频加到网页上 目前只学习到embed可以加载成功网上的视频和图片 代码如下 更多的关于图片格式问题 以及使用embed加载本地视频 图片仍待研
  • 学习笔记 JavaScript ES6 深拷贝与浅拷贝

    学习内容 如何把一个对象复制给另一个对象 如何把一个对象复制给另一个对象 浅拷贝的可以理解为对象拷贝成功后 当有一个对象发生变化时 另一个也受到影响 这个主要是与引用地址有关 用Object assign 方法进行拷贝不安全 是潜拷贝 来看
  • Spring源码从入门到精通---@Value赋值(十)

    上篇文章介绍了BeanPostProcessor spring框架生命周期 PostConstruct PreDestroy InitializingBean disposableBean接口 依赖注入 Autowird都离不开这个接口 B
  • Python兼职五天赚3000,方法经验分享,带你实现财富自由!!!

    大学生用Python兼职五天狂赚1200 方法经验分享 让你早日实现财富自由 现在的年轻人虽然表面风光 可是却很难攒下钱 这一类人被定义为 隐形贫困者 原因是什么呢 根据小编的分析 现在人们对生活质量要求普遍提高了 但是工资没有跟得上自己的
  • 正则表达式之-验证邮箱

    一 邮箱正则概述 昨天在开发项目的时候 有一个验证邮箱的需求 本来想着自己写一个正则表达式来验证的 但是写的时候却发现很多关于正则的知识都忘的差不多了 有些东西想记住真的就要重复练习 看明白了没有用 要天天练习才行 于是在参考了众多资料之后
  • 使用servlet处理HTTP响应

    1 解释http协议中包含哪几部份 状态行 放的是协议 版本 状态码描述 响应头 键 值 空行 内容 消息体 2 解释状态行的作用 有哪些常见的状态码 分别代表什么含义 状态行作用在Servlet中设置状态码来实现许多重要功能 状态码 20
  • JPA @Id 和 @GeneratedValue 注解介绍

    转载自一个404页面 Id Id 注解用于声明一个实体类的属性映射为数据库的主键列 该属性通常置于属性声明语句之前 可与声明语句同行 也可写在单独行上 Id标注也可置于属性的getter方法之前 GeneratedValue Generat
  • OPPO/真我手机ColorOS13系统解账户锁-移除手机密码图案锁方法

    在搞机之前 请确定自己的手机不是非法获取 本文只讲叙ColorOS13系统解锁方法 仅为个人测试研究出来的经验 未对官方系统进行任何修改 只推荐专业维修师傅从维修的角度进行解锁 不推荐个人用户对非自己的手机进行非法破解 产生任何违法行为需要
  • 第十四届蓝桥杯三月真题刷题训练——第 27 天

    目录 第 1题 数学考试 前缀和 代码 第 2 题 地标访问 二分 题目背景 题目描述 输入格式 输出格式 输入输出样例 说明 提示 代码 第 1题 数学考试 前缀和 代码 package 第十四届蓝桥杯三月真题刷题训练 day27 imp
  • 常见文件文件头

    各类文件的文件头标志 1 从Ultra edit 32中提取出来的 附件 文件格式分析器 JPEG jpg 文件头 FFD8FF PNG png 文件头 89504E47 GIF gif 文件头 47494638 TIFF tif 文件头
  • NAT介绍

    在传统TCP IP通信过程中 所有的路由器仅仅是充当一个中间人的角色 也就是通常 所说的存储转发 即路由器不会对转发的数据包进行修改 准确地讲 除了将源MAC地址 换成自己的MAC地址以外 路由器不会对转发的数据包做任何修改 而NAT恰恰是
  • J2EE-007 反射+BaseServlet实现

    因为之前都是用的幕布进行文章发布现在移动到这里进行统一管理 文章我就不再编写了 后面会用CSDN进行编写 望审核给与通过谢谢 https share mubu com doc 3hGLbDxQhQV
  • Linux进程间通信——eventfd

    Table of Contents 什么是eventfd 创建eventfd 读eventfd 写eventfd 使用例子 什么是eventfd eventfd是Linux 2 6提供的一种系统调用 它可以用来实现事件通知 eventfd包
  • 《深入理解计算机系统》实验五Cache Lab

    前言 深入理解计算机系统 实验五Cache Lab下载和官方文档机翻请看 https blog csdn net weixin 43362650 article details 121989400 我觉得这个文档对整个实验很有帮助 对于我来