计组《高速缓存》四组联16行+LRU替换实例

因为在上的计算机系统基础课程讲到这儿了顺便还有个实验,就总结一下高速缓存和内存交互的过程。

何为高速缓存?

高速缓存,简称cache。

我们知道,常用的32位计算机上的内存条可以存储000…000-111…1111也就是2^32大小的键值对,key为32位的地址,value为32位的值。当cpu通过MMU想要访问内存某个地址的时候,主存就会返回相应地址的值。

内存中的数据通常是以块为单位存储的,一个块是什么呢?一个块的大小若为4bytes,那么就刚好占了某一个32位地址的32位值。

如果cpu直接从内存中读取值,这个速度会很慢很慢,于是就有聪明的B想出了高速缓存(Cache)的办法:在cpu到内存中读取数据过程之间增设一个cache数组,这个cache数组负责从内存中读取数据块然后返回给cpu。

那么为什么要有这么多余的一步呢?直接从内存中读取不香吗?原因有2点:

  1. 高速缓存从硬件上来说速度快于直接从内存中取
  2. cpu某一时刻只能执行一个进程,这个进程对内存的需求是很局限的,你把这个进程需要的数据块都缓存好,那读的时候就很快很方便了。

高速缓存如何实现

在说实现之前,首先要说明一下内存地址的构成和高速缓存行的构成。

内存地址 :Tag + 组号 + 块内字节偏移
高速缓存行(Cache line):有效位 + tag + 块数据

so,cache是如何从内存中取读取数据的呢?

高速缓存的单位是cache line,可以分为n组,这里就拿4组来举例说明,如果有64个cache line,总共分为4组,那么每组也就有16个cache line。对于内存中的块来说,第n块只能存在n mod 4 组内。

当cpu想要取一个内存地址的数据时,会现在高速缓存中第n组找,这个n就对应主存地址的组号,条件是 有效位=1,cache line 的tag = 内存地址的tag,如果满足条件就说明找到了,那么就根据块内数据的字节偏移返还所需要的字节。

直接一说可能很懵逼,举个简单的例子:

假设内存地址有10位,cache lines被分为4组,一共16个cache line,也就是一组有4个line,内存中一个块大小为4bytes

那么,如何知道内存地址的10位中tag占几位?组号占几位?块内字节偏移占几位呢?首先总共有4组,那么也就是说,我用2^2也就是00-11也就是2位就可以表示组数了,而一个块共有4个bytes,若想要表示出第n个字节,也只需要00-11也就是2个bit就可以表示出第几个字节了,所以tag位有10-2-2=6位。

还有个问题,为什么主存的地址 是 tag在最前,组号在中间,块内字节偏移在最后呢? 如果把内存中的所有数据块按照每行4个拍成二维数组,那么每一行就对应tag,每一列就对应组号,块号 = tag*4+组号 。 而确定了哪一块,最后才能确定块的哪一个字节。

了解了主存的地址的细节由来,那么就可以继续了解cache读取内存的细节了:

还是上面的例子,当cpu想取地址为 0000081111 的字节时,首先 tag = 000008 , 组号 = 3, 块内字节偏移 = 3 。根据上面谈到的算法过程:首先cache先定位到第3组,也就是cache数组中cache[12]-cache[15](第三组的四个cache line中去)。 查找这4个cache line有没有满足flag = 1 并且tag = 内存的tag。

若有则表示这个cache line的32位数据块就存着所需要的主存地址的数据块,接着再取这个数据块的第3个字节返还给cpu。

若无,那么cache需要做的就是寻找这一个组的牺牲的cache line来保存所需要查找的主存块。当然了,这个替换策略有许多种,这里介绍LRU(least recently use)算法实现的替换策略:

需要在cache line中添加一个计数器count
规则为:
在这里插入图片描述
代码:

#include<iostream>
#include<cstring>
#include<stdlib.h>
using namespace std;
struct node{
	char Valid='0'; //有效位 令'1'有效,'0'无效 
	char CacheTag[6]; //标识位(组内条目偏移)
	char CacheData[32]; //数据块 
	int count;   //用于LRU统计次数 
}cache[16]; //16 lines 

char replaceItem[32];

//用来避免直接给char[]用字符串赋值末尾的自动补'\0'的情况 
void assign(char a[],string x){
	for(int i=0;i<x.size();i++){
		a[i]=x[i];
	}
}

//用于把一个char[]赋值给另一个char[] 
void strCopy(char source[],char destination[],int length){
	for(int i=0;i<length;i++){
		destination[i]=source[i];
	}
}

//若找到时刷新方法
void fresh1(int begin,int end,int vis){
	int visCount = cache[vis].count;
	for(int i=begin;i<end;i++){
		if(i!=vis&&cache[i].count<visCount&&cache[i].Valid=='1'){
			cache[i].count++;
		}
	}
	cache[vis].count=0;
//	cout<<"run fresh1!~"<<endl;
}

//若未找到时的刷新方法 
void fresh2(int begin,int end,int i){
	cache[i].count=0;
	for(int j=begin;j<end;j++){
		if(j!=i&&cache[j].Valid=='1'){
			cache[j].count++;
		}
	}
}

//判断组内是否还有空的cache line 
int judgeIsFull(int begin,int end){
	for(int i=begin;i<end;i++){
		//未命中且该组未满 
		if(cache[i].Valid=='0'){
			cout<<"未命中且该组未满,放入cache["<<i<<"]"<<endl; 
			cache[i].Valid = '1';
			fresh2(begin,end,i);
			return i;
		}
	}
	
	for(int i=begin;i<end;i++){
		//未命中且该组已满,i被淘汰替换~ 
		if(cache[i].Valid=='1'&&cache[i].count==3){
			cout<<"未命中且该组已满,替换cache["<<i<<"]"<<endl; 
			fresh2(begin,end,i);
			return i;
		}
	} 
	return -1; 
}


char* Cache(char binary[]){
	char *result = (char*)malloc(32*sizeof(char));
	int groupIndex = ((binary[6]-'0')<<1)+binary[7]-'0';
	//search group's cache line
	int begin = groupIndex<<2,end = (groupIndex+1)<<2;
	for(int i=begin;i<end;i++){
		node* tmpCache = new node;
		tmpCache = &cache[i];
		int flag1 = tmpCache->Valid-'0';
		int flag2 = 1;
		char* CacheTag = tmpCache->CacheTag; 
		for(int j=0;j<6;j++){
			if(CacheTag[j]!=binary[j]){
				flag2 = 0;
				break;
			}
		}
		//如果找到了,则返回 
		if(flag1&flag2){
			result = &tmpCache->CacheData[0];
			fresh1(begin,end,i);
			cout<<"find it!"<<endl;
			return result;
		}
	}
	//if not found,then replace as LRU
	cout<< "not found!~";
	int newGroupIndex = judgeIsFull(begin,end);
	//replace CacheTag
	strCopy(binary,cache[newGroupIndex].CacheTag,6);
	//replace CacheData
	strCopy(replaceItem,cache[newGroupIndex].CacheData,32);

	return cache[newGroupIndex].CacheData;
}

void printStr(char* x,int size){
	for(int i=0;i<size;i++){
		cout<<x[i];
	}
}


int main(){
	
	assign(replaceItem,"10001000100010001111111111111111");
	
	char binary[10];
	string x;
	while(1){
		printf("请输入10位内存,输入exit退出\n");
		cin>>x;
		if(x.size()!=10) break;
		assign(binary,x);
		char* result = Cache(binary);
		cout<<"binary = ";
		printStr(binary,10);
		cout<<endl<<"result = ";
		printStr(result,32);
		cout<<endl;
//	测试每次运行之后第0组每个cache line的count	
//		for(int i=0;i<4;i++){
//			printf("cache[%d].count = %d,",i,cache[i].count);
//		}
//		cout<<endl;
	}
	
	/*
	测试数据:
	 binary1 = 0000010000 //第0组第0个 
	 binary2 = 0000010000 //第0组第0个 
	 binary3 = 0000100000 //第0组第1个 
	 binary4 = 0000110000 //第0组第2个 
	 binary5 = 0001000000 //第0组第3个 
	 binary6 = 0001010000 //第0组第4个,将替换掉第0个 
	*/
	
	return 0;
} 

结果:
在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值