如何编程模拟计算机中的高速缓存

嵌入式ARM 2021-02-20 00:00


  • 1. 实验要求

  • 2. 编程

    • 2.1 读取文件

    • 2.2 高速缓存定义结构体

    • 2.3 初始化Cache

    • 2.4 解析输入的指令

    • 2.5 LRU策略

    • 2.6 更新高速缓存Cache

    • 2.7 完整代码

  • 3. 测试结果

1. 实验要求

  1.编程模拟Cahce的命中,不命中,替换等行为。

  2.编写的程序必须对任意s,E和b正确工作。

  3.本实验不涉及真实的数据读写,不需要考虑block的细节,每行只有一个block。

  4.编写的程序要能读取指定文件内的指令,根据不同的指令完成不同的动作,下面为指令内容示例。

I 0400d7d4,8      #
M 0421c7f0,4      # 修改access cache, 即读写access cache两次,0421c7f0:修改的地址,8:修改的长度
L 04f6b868,8      # 读access cache,04f6b868:读取的地址,8:读取长度
S 7ff0005c8,8     # 写access cache,0421c7f0:写的地址,8:写的长度

2. 编程

  考虑模拟一个Cache的行为需要用到哪些变量?

计算机中的高速缓存模型

  Cache有组数S、一组包含的行数E,存储块的字节大小B,Cache的容量C=S×E×B。

  地址的构成:标识位t、组索引s、块偏移b(前面说了,不需要管块偏移)。

  关于缓存和内存数据交换的详细介绍可以看下这个24张图7000字详解计算机中的高速缓存。

  下面我们开始编写代码。

2.1 读取文件

  getopt()该函数能够帮助程序分析C语言命令行程序输入的参数。

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

  重点说下getopt()函数。前两个形参是main函数传入的参数,即我们输入的命令行,第三个形参是 optstring“选项字符串”,即标识哪些字母表示了操作。

  如"a:b:cd::e",字母后带一个冒号(例中的a、b)表明这个操作带参数,字母后的内容需要读取,存放到它内部变量 extern char * optarg中。

  字母不带冒号(例中的c、e)表明该操作不带参数,后面输入的内容仍看作操作符处理。字母后带两个冒号(例中的d)表明该操作后参数是可选的,但是要求如果带参数时参数与操作符不能有空格,如-d123是对的,而-d 123会报错。当读取了全部的输入的命令后 getopt()返回-1。

while((opt = getopt(argc,argv,"s:E:b:t:")) !=-1){           //解析命令行参数
  switch(opt){
  case 's':
   s=atoi(optarg);
   break;
  case 'E':
   E=atoi(optarg);
   break;
  case 'b':
   b=atoi(optarg);
   break;
  case 't':
   filepath = optarg;
   break;
  }
 }

   fscanf函数,该函数能够帮助用户处理文本文件中输入的格式化数据。

int fscanf(FILE *stream, char *format[,argument...]);

  stream-这是指向 FILE 对象的指针,该 FILE 对象标识了流。

  format-这是 C 字符串,包含了以下各项中的一个或多个:空格字符、非空格字符和format 说明符。

2.2 高速缓存定义结构体

  实验要求中说明了,不需要处理b,只需认为每行中有一个block。因此cache_line结构体中包括有效位,标记位,时间戳三个变量就够了。stamp记录的是block 的使用时间,每被使用一次,block++。因此,stamp越大表明该block越是最近被使用。具体代码如下。

typedef struct{
 int valid_bits;
 unsigned  tag;
 int stamp;
}cache_line;

2.3 初始化Cache

  定义一个Cache[S] [E]大小的二维数组。这样Cache就模拟好了。

void init(){
 cache = (cache_line**)malloc(sizeof(cache_line*)*S);             //malloc开辟空间
 for(int i=0;i<S;i++)
  *(cache+i) = (cache_line*)malloc(sizeof(cache_line)*E);
 for(int i=0;i<S;i++){
  for(int j=0;j<E;j++){
   cache[i][j].valid_bits = 0;             // 初始化有效位为0
   cache[i][j].tag = 0xffffffff;           //初始化标记位
   cache[i][j].stamp = 0;                  //这个时间戳模拟LRU的时候会用到  
  } 
 }
}

2.4 解析输入的指令

  先分析每个输入的指令应该被如何操作。如果是I,则不是数据操作,直接忽略。如果是L或者S,则需要进行一次hit-miss-eviction检测,如果是M,则相当于先L再S,需要进行两次hit-miss-eviction检测。然后考虑hit-miss- eviction检测细节。

 while(fscanf(file," %c %x,%d",&operation,&address,&size)>0){
  switch(operation){
   case 'L':
    update(address);
    break;
   case 'M':
    update(address);
   case 'S':
    update(address);
    break;
  }
  time();
 }

  首先需要对读取的地进有分析,低b位表示 block偏移,本实验中不需要计算block偏移。中间s位是 set index位,表示对那个行操作。其余t位是tag位。用于标明对应的line是否有效。我们需要对得到的地址进行如下操作,解析出t和s。

 unsigned s_address =(address>>b) & ((0xffffffff)>>(32-s));                //索引位
 unsigned t_address = address>>(s+b);                                     //标记位

2.5 LRU策略

  替换策略使用的是LRU的缓存替换策略。如果该set存满了,我每次要找到stamp最小的替换。为了方便,我把stamp初始化为0,之后每个操作+1. 当stamp= 0的时候就代表不valid。

void time(){
 for(int i=0;i<S;i++){
  for(int j=0;j<E;j++){
   if(cache[i][j].valid_bits == 1)
    cache[i][j].stamp++;  
  } 
 }
}

 for(int i=0;i<E;i++){
  if(cache[s_address][i].stamp > max_stamp){
   max_stamp = cache[s_address][i].stamp;
   max_i = i;
  }
 }

2.6 更新高速缓存Cache

  Cache的容量有限,当满的时候需要替换行,先遍历当前组,判断它满了没有,如何判断是否满,可以遍历所有的行,只要有一个有效位为0,(有效位的作用是说明该行是否存储了数据,通俗的理解就是是否为空)则该组未满。

for(int i=0;i<E;i++){
 if(cache[s_address][i].valid_bits == 0){
  cache[s_address][i].tag = t_address;
  cache[s_address][i].valid_bits = 1;
  cache[s_address][i].stamp = 0;       
  miss++;
  return;
 }
}

2.7 完整代码

/*
 * @Description: 编程模拟Cache
 * @Version: V1.0
 * @Autor: 嵌入式与Linux那些事
 * @Date: 2021-1-1 20:40:12
 * @LastEditors: 嵌入式与Linux那些事
 * @LastEditTime: 2021-1-1 22:11:58
 */

#include "cachelab.h"
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <stddef.h>
typedef struct{
 int valid_bits;
 unsigned  tag;
 int stamp;
}cache_line;
char* filepath = NULL;
int s,E,b,S;                          // s 表示组 ,E表示行,每一行有 2^b位 ,S = 2^s 组
int hit=0,miss=0,eviction=0;
cache_line** cache = NULL;

void init(){
 cache = (cache_line**)malloc(sizeof(cache_line*)*S);             //malloc开辟空间
 for(int i=0;i<S;i++)
  *(cache+i) = (cache_line*)malloc(sizeof(cache_line)*E);
 for(int i=0;i<S;i++){
  for(int j=0;j<E;j++){
   cache[i][j].valid_bits = 0;             // 初始化有效位为0
   cache[i][j].tag = 0xffffffff;           //初始化标记位
   cache[i][j].stamp = 0;                  //这个时间戳模拟LRU的时候会用到  
  } 
 }
}

void update(unsigned address){
 unsigned s_address =(address>>b) & ((0xffffffff)>>(32-s));           //从地址分解出索引位
 unsigned t_address = address>>(s+b);                                 //从地址分解出标记位
 //判断tag为是否相等,是否命中
    for(int i=0;i<E;i++){
  if((*(cache+s_address)+i)->tag ==t_address){
   cache[s_address][i].stamp = 0;       //被使用了
   hit++;
   return;
  }
 }
    //更新高速缓存cache
 for(int i=0;i<E;i++){
  if(cache[s_address][i].valid_bits == 0){
   cache[s_address][i].tag = t_address;
   cache[s_address][i].valid_bits = 1;
   cache[s_address][i].stamp = 0;       //now ,this is load
   miss++;
   return;
  }
 }
    //暴力实现LRU
 int max_stamp=0;
 int max_i;
 for(int i=0;i<E;i++){
  if(cache[s_address][i].stamp > max_stamp){
   max_stamp = cache[s_address][i].stamp;
   max_i = i;
  }
 }
 eviction++;
 miss++;
 cache[s_address][max_i].tag = t_address;
 cache[s_address][max_i].stamp = 0
}
//更新stamp
void time(){
 for(int i=0;i<S;i++){
  for(int j=0;j<E;j++){
   if(cache[i][j].valid_bits == 1)
    cache[i][j].stamp++;  
  } 
 }
}
int main(int argc,char *argv[])
{
 int opt;         
 while((opt = getopt(argc,argv,"s:E:b:t:")) !=-1){           //解析命令行参数
  switch(opt){
  case 's':
   s=atoi(optarg);
   break;
  case 'E':
   E=atoi(optarg);
   break;
  case 'b':
   b=atoi(optarg);
   break;
  case 't':
   filepath = optarg;
   break;
  }
 }
 S = 1<<s;
 init();
 FILE* file=fopen(filepath,"r");
 if(file == NULL){                       // 读取文件错误
  printf("Open file wrong");  
  exit(-1);
 }
 char operation;
 unsigned address;
 int size; 
 while(fscanf(file," %c %x,%d",&operation,&address,&size)>0){
  switch(operation){
   case 'L':
    update(address);
    break;
   case 'M':
    update(address);
   case 'S':
    update(address);
    break;
  }
  time();
 }
 for(int i=0;i<S;i++)                  //释放 cache[S][E]
  free(*(cache+i));
 free(cache);
 fclose(file);                     //关闭文件
    printSummary(hit,miss,eviction);    //本次实验使用的是HNU的自动评分系统,它会读取这条语句进行评分。
    return 0;
}

3. 测试结果

HNU的自动评分系统会测试我们编写的代码是否可以通过所有的测试用例,和Reference simulator的对比表明,结果完全相同。测试通过!

image-20201231160527635

END

来源:嵌入式与Linux那些事,作者:嵌入式那些事

版权归原作者所有,如有侵权,请联系删除。

推荐阅读

树莓派Pico:仅4美元的MCU

嵌入式Linux开发板裸机程序烧写方法总结

国产16位MCU的痛点,可以用这款物美价廉产品


→点关注,不迷路←

嵌入式ARM 关注这个时代最火的嵌入式ARM,你想知道的都在这里。
评论
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 80浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 51浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 94浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 37浏览
  • 本文介绍编译Android13 ROOT权限固件的方法,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。关闭selinux修改此文件("+"号为修改内容)device/rockchip/common/BoardConfig.mkBOARD_BOOT_HEADER_VERSION ?= 2BOARD_MKBOOTIMG_ARGS :=BOARD_PREBUILT_DTB
    Industio_触觉智能 2025-01-08 00:06 100浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 38浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 51浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 33浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 82浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 39浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦