默默的打开了我的《计算机组成原理》崭新的课本,上个月老师网课讲的内容还是记忆犹新
可以先了解下计算机系统概述
冯诺依曼结构体系的构成有着这样五个部分
其中运算器与控制器构成了CPU,输入设备与输出设备属于适配器
而存储器的要求就是,容量大,速度快,成本低
计算机中对于存储器需要满足的三个特点,就是容量大,速度快,成本低。但是在存储器的容量非常大的情况下,想要满足每次取数据的速度都很快,这是非常难的。
为了解决这个矛盾的问题,计算机系统采用了多级存储体系的结构,即使用高速缓冲存储器(cache),主存储器和外存储器。
简称主存,是计算机系统的主要存储器,用来存放计算机运行期间的大量程序和数据,主要是由MOS半导体存储器组成。
他可以和cache交换指令和数据。
简称外存,他是大容量的辅助存储器。目前主要使用磁盘存储器,磁带存储器,光盘存储器。
外存的特点是存储容量大,位成本低,通常用来存放系统程序和大型数据文件及数据库
简称cache,他是计算机系统中的一个高速,小容量,半导体存储器
他的存在就是为了提高计算机的处理速度,利用了cache的高速存取指令和数据。和主存储器相比,他的存取速度快,但是存储容量小。
当处理器想要读取主存储器中的数据时,先检查这个数据是否在高速缓冲存储器中。
由于访问的局部性现象存在,所以当一块数据被取入高速缓冲存储器来满足一次存储器访问时,很可能之后会与多次访问的数据就是该数据周围的数据
cache的工作原理要求尽量保存最新的数据。当一个新的主存块需要拷贝到cache,而cache中运行存放的位置全部都被占满了,这时就需要发生
替换
替换有三种算法
LRU算法将近期内长久没有被访问的数据替换出来。为每个数据设置一个计数器,cache每命中一次,命中的数据计数器清零,其他计数器+1。当需要替换的时候,就把计数器值最大的数据替换出来。
使用的方法:一个带头结点的双向循环链表
https://leetcode-cn.com/problems/lru-cache/
struct node
{
node* _pre;
node* _next;
int _key;
int _value;
node(int key,int value)
:_key(key)
,_value(value)
,_pre(NULL)
,_next(NULL)
{}
};
class LRUCache {
public:
LRUCache(int capacity) {
_capacity = capacity;
head = new node(-1,-1);
head->_next = head->_pre = head;
}
~LRUCache()
{
delete head;
_capacity = -1;
}
//从链表中移除该节点
void RemoveNode(node* cur)
{
cur->_next->_pre = cur->_pre;
cur->_pre->_next = cur->_next;
}
//头插
void PushFront(node* cur)
{
head->_next->_pre = cur;
cur->_next = head->_next;
head->_next = cur;
cur->_pre = head;
}
int get(int key) {
if(map.find(key) != map.end())
{
node* cur = map[key];
//移除该节点
RemoveNode(cur);
//把该节点放到头部
PushFront(cur);
return cur->_value;
}
return -1;
}
void put(int key, int value) {
//如果存在key
if(map.find(key) != map.end())
{
node* cur = map[key];
//移除
RemoveNode(cur);
//头插
PushFront(cur);
cur->_value = value;
return ;
}
//这里不存在key,value
//判断内存是否满了
if(map.size() == _capacity)
{
//移除队尾元素
node* tail = head->_pre;
RemoveNode(tail);
map.erase(tail->_key);
delete tail;
}
//队头插入key
node* cur = new node(key,value);
PushFront(cur);
map[key] = cur;
}
private:
int _capacity;
node* head;
unordered_map<int,node*> map;
};
LFU算法认为,应将这段时间内访问次数最少的数据替换出。为此给每个数据设置一个计数器,每访问一次,计数器的值+1。当发送冲突的时候,就找到当前计数器的值最小的那一个数据,把这个数据替换成新的元素。
使用的方法:两个哈希表
https://leetcode-cn.com/problems/lfu-cache/submissions/
struct node
{
int _key;
int _value;
int _freq;//出现的频率
node(int key,int value,int _freq)
:_key(key)
,_value(value)
,_freq(_freq)
{}
};
class LFUCache {
public:
LFUCache(int capacity) {
freq_map.clear();
key_map.clear();
_minfreq = 0;
_capacity = capacity;
}
~LFUCache()
{
freq_map.clear();
key_map.clear();
_capacity = _minfreq = -1;
}
int get(int key) {
//如果容量为0 ,直接退出
if(_capacity == 0) return -1;
//如果当前值不存在,也直接退出
if(key_map.find(key) == key_map.end()) return -1;
list<node>::iterator cur = key_map[key];
int val = cur->_value;
int freq = cur->_freq;
//移除key的节点,在freq的使用频率链表中
freq_map[freq].erase(cur);
//如果当前链表删除后为空
if(freq_map[freq].size() == 0)
{
freq_map.erase(freq);
if(_minfreq == freq) _minfreq++;
}
//插入key的点到 freq+1 的出现频率中,头插法
freq_map[freq+1].push_front(node(key,val,freq+1));
key_map[key] = freq_map[freq+1].begin();
return val;
}
void put(int key, int value) {
if(_capacity == 0) return ;
//如果存在Key
if(key_map.find(key) != key_map.end())
{
list<node>::iterator cur = key_map[key];
int freq = cur->_freq;
freq_map[freq].erase(cur);
if(freq_map[freq].size() == 0)
{
freq_map.erase(freq);
if(_minfreq == freq) _minfreq++;
}
freq_map[freq+1].push_front(node(key,value,freq+1));
key_map[key] = freq_map[freq+1].begin();
return ;
}
//此处是不存在key,需要插入的情况
if(key_map.size() == _capacity)
{
//链表已经满了
//通过当前的最小访问频率,拿到链表的尾部数据
node tail = freq_map[_minfreq].back();
key_map.erase(tail._key);
freq_map[_minfreq].pop_back();
if(freq_map[_minfreq].size() == 0) freq_map.erase(_minfreq);
}
_minfreq = 1;//更新当前最小访问次数
freq_map[_minfreq].push_front(node(key,value,1));//新结点值访问了一次
key_map[key] = freq_map[_minfreq].begin();
}
private:
unordered_map<int,list<node> > freq_map; //以出现的频率来建图
unordered_map<int,list<node>::iterator> key_map; //通过key值找到这个节点的迭代器
int _minfreq; //当前最小使用次数
int _capacity;//容量
};