导学
- 之前了解了高速缓存,为了提高效率,我们希望命中率越高越好,这需要合理的调度算法
- 本篇主要实现双向链表,实现常见的缓存调度策略
双向链表
- 这部分属于数据结构的内容,但学以致用,在这里实现
- 不详细介绍概念了,优点(相较于单链表):
- 可以快速找到当前节点的上一个节点
- 可以快速去掉链表中的某一个节点(查找快)
链表节点
- 存放key-value
- 用python实现,打开PyCharm
- 节点Node使用类实现:
DoubleLinkedList
- 初始化链表:容量、头尾指针
- 正常的对象方法逻辑:参数/返回值、判空、业务逻辑
- 节点操作可以画示意图(前后指针一定要有着落)
#!usr/bin/env python #-*- coding:utf-8 _*- """ @author:瑞哥 Summarize: """ class Node: ''' 定义链表节点 params: key 键 value 值 prev 前驱指针 next 后继指针 ''' def __init__(self, key,value): self.key = key self.value = value self.prev = None self.next = None def __str__(self): # 输出对象信息,面向用户:例如使用print()时 return '{%d: %d}'%(self.key,self.value) def __repr__(self): # 返回类对象的信息,面向开发者:控制台显示 return '{%d: %d}' %(self.key, self.value) class DoubleLinkedList: ''' 定义双向链表及方法 ''' def __init__(self, capacity): ''' :param capacity: 链表最大容量 head 头节点指针 tail 尾节点指针 size 当前节点数 ''' self.capacity = capacity self.head = None self.tail = None self.size = 0 def __add_head(self, node): # 双前置下划线,类似private;单前置下划线,私有化属性和方法,import * 禁止导入,类似protected ''' 头部添加节点 :param node: 要保存的节点 :return: node ''' if not self.head: self.head = node self.tail = node self.head.next = None self.head.prev = None else: node.next = self.head node.prev = None self.head.prev = node self.head = node # 头指针 self.size +=1 return node def append_front(self, node): self.__add_head(node) def __add_tail(self, node): ''' 尾部添加节点 :param node: :return: ''' if not self.head: self.head = node self.tail = node self.tail.next = None self.tail.prev = None else: self.tail.next = node node.prev = self.tail self.tail = node # 重置尾指针 self.tail.next = None self.size += 1 return node def append(self, node): return self.__add_tail(node) def __del_tail(self): ''' 删除尾部元素,弹出尾部节点 :return: ''' if self.tail == None: return node = self.tail # 准备返回 if not node.prev: self.head=self.tail = None else: self.tail = node.prev self.tail.next = None self.size -= 1 return node def __del_head(self): ''' 删除头部元素,也相当于弹出头部节点 :return: ''' if not self.head: return node = self.head # 准备返回 if not node.next: self.head=self.tail = None else: self.head = node.next self.head.prev = None self.size -= 1 return node def pop(self): return self.__del_head() def __remove(self, node=None): ''' 删除任意节点;这里Node单独创建,放在列表中,所以移除时可直接传入node,而不是通过传入value直接创建好链表,还得找元素! :param node:传入节点指针 :return: 任何一门语言,数据类型、数据结构都是基础 ''' if not node: # None 它属于NoneType类型(空值),None也是NoneType数据类型的唯一值(常用来判断无返回值的情况,或者初始化指针);与空列表、空字符串是不一样的(讲究个数据类型!) # 可将其理解成指向特殊的“空”,在C中是null,可表示未初始化,查看内存为 cc cc cc cc,但并不是野指针 node = self.tail # 默认删除尾部元素 if node==self.tail: self.__del_tail() elif node==self.head: self.__del_head() else: node.prev.next = node.next node.next.prev = node.prev self.size -= 1 return node def remove(self, node=None): # 节点指针可能为空 return self.__remove(node) def print(self): ''' 重载输出方法 :return: ''' p = self.head line = '' while p: line += '%s'%p # __str__ p = p.next if p: line += '=>' print(line) if __name__ == '__main__': line = DoubleLinkedList(10) nodes = [] for i in range(10): # 准备kv对 node = Node(i, i) # k-v nodes.append(node) line.append(nodes[0]) line.print() line.append(nodes[1]) line.print() line.append_front(nodes[3]) line.print() line.remove(nodes[1]) line.print() # 从规范来讲,应将外部调用方法统一写在一起
- 这里的方法后面要用到
- 注意
remove()
,并不是删除,而是断开链接,node以独立的对象存在
FIFO
- 使用双向链表实现FIFO算法
- 关键在于
put
方法,要考虑到字块已存在和缓存满 - 使用字典保存KV,方便查找
#-*- coding:utf-8 _*- """ @author:瑞哥 @file: FIFO.py Summarize: """ from lru.Double import DoubleLinkedList, Node class FIFOCache: ''' 先进先出cache置换算法 ''' def __init__(self, capacity): self.capacity = capacity self.size = 0 self.map = {} # dict:key=node.key value = node 方便查找 self.list = DoubleLinkedList(capacity) # 存储KV的队列 # CPU取缓存 def get(self, key): if key not in self.map: return -1 else: node = self.map.get(key) return node.value def put(self,key,value): if self.capacity == 0: return -1 if key in self.map: # 加入的元素在队列中,移到尾部(后加入的即最近使用的,后移出缓存) node = self.map.get(key) self.list.remove(node) node.value = value self.list.append(node) else: if self.size == self.capacity: node = self.list.pop() # __del_head del self.map[node.key] # 字典方法 del node = Node(key, value) self.list.append(node) self.map[key] = node self.size += 1 def print(self): self.list.print() if __name__ == '__main__': cache = FIFOCache(2) cache.put(1, 1) cache.put(2, 2) cache.print() print(cache.get(1)) cache.put(3, 3) # 移出1 1 cache.print()
LRU
- 这个算法普遍采用,很重要
- 算法思想:
- 代码结构和FIFO类似;
- 这里的关键点在
get()
中,每次被CPU get后,此节点append_front()
put
已存在节点时,执行get即可
#!usr/bin/env python #-*- coding:utf-8 _*- """ @author:瑞哥 @file: LRU.py @time: 2021/03/23 Summarize: """ from lru.Double import DoubleLinkedList, Node class LRU: def __init__(self, capacity): self.capacity = capacity self.map = {} self.list = DoubleLinkedList(capacity) def get(self, key): if key in self.map: # self.map.keys() 字典的方法很多 node = self.map[key] # 这里涉及Python的深浅拷贝,这里属于直接赋值,map中的node是引用,所以能这样取node,如果是深拷贝是不能直接操作此node的 self.list.remove(node) # 默认del tail self.list.append_front(node) return node.value else: return -1 def put(self, key, value): if key in self.map: self.get(key) # 调用即可更新位置到队首 else: node = Node(key, value) if self.list.size >= self.capacity: old_node = self.list.remove() # 默认删除tail self.map.pop(old_node.key) # 更新map self.list.append(node) self.map[key] = node def print(self): self.list.print() if __name__ == '__main__': cache = LRU(3) cache.put(1, 1) cache.put(2, 2) cache.put(3, 3) cache.print() print(cache.get(3)) cache.print() cache.put(2, 2) cache.print()
- 这里的关键点在
小结
- 本篇先实现了双向链表,Node对象简化了节点操作;FIFO和LRU等算法的核心都是get/put方法,LRU操作节点的顺序实现了缓存的合理调度
- 计算机组成原理和操作系统是相辅相成的,在指令的执行流程部分还有很多细节需要了解(必考),例如虚拟内存、微操作等,就总结在OS