一.基础认知
1.个人理解
替换算法是用于管理高速缓存(Cache)中数据的一种策略,当高速缓存已满并需要为新的数据腾出空间时,替换算法会决定哪些数据应该被从高速缓存中替换出去。
2.基础认知
首先,我们需要知道计算机的组成原理,在其中计算机可以划分为cache-主存和主存-辅存两种层级结构,而平时CPU是只能访问主存中的内容。为了解决访问主存的速度和主存容量的问题,提出了两种技术方法:高速缓存和虚拟存储器。
其中我们需要了解一点:CPU与Cache或主存之间的数据交换以字为单位,Cache和主存之间的数据交换以块为单位,而主存与辅存之间的数据交换以段或页为单位。
根据程序的局部性原理,了解了高速缓存与虚拟存储器技术后,我们可以知道高速缓存(Cache)是主存内容的一个副本,在虚拟存储器同样有类似的道理,如果Cache满了之后需要进入新的块,就需要用到替换算法了。
3.常用的替换算法
- 最近最少使用(LRU)
- 先进先出(FIFO)
- 随机替换算法
- 最不经常使用算法(LFU)
- 考虑时间因素的替换算法
下面针对这几种算法进行详细的举例介绍。
二.LRU
1.算法原理
如果一个数据在最近一段时间内没有被访问过,那么在将来它被访问的概率也很小。因此,当缓存满时,应该优先淘汰最近最少使用的数据。
2.编程思路
LRU算法维护了一个有序列表,列表的头部是最近最少使用的数据,当需要淘汰数据时,直接删除头部的数据即可。每次访问数据时,如果数据已经在列表中,就将其移动到队尾;如果不在列表中,则将其插入到队尾。这样可以保证队尾的数据是最近被访问的数据,队头的数据是最少被访问的数据。
LRU算法的实现可以通过哈希表和双向链表相结合来完成。哈希表用于快速定位数据是否在列表中,双向链表用于维护数据的顺序。当访问数据时,可以先在哈希表中查找数据是否存在,如果存在,则将数据移到队尾;如果不存在,则将数据插入到队尾,并在哈希表中添加对应的键值对。当需要淘汰数据时,直接删除队头节点即可。
LRU算法的时间复杂度为O(1),空间复杂度为O(n),其中n为缓存的大小。
3.优缺点
1.优点:
-
实现简单:LRU算法的实现非常简单,只需要维护一个队列即可。
-
缓存命中率高:LRU算法能够很好地利用局部性原理,保留最近经常使用的数据,从而提高缓存命中率。
-
适应各种访问模式:LRU算法适用于各种访问模式,包括顺序访问、随机访问和局部性较强的访问模式。
2.缺点:
-
时间复杂度高:LRU算法在实现过程中需要对缓存进行频繁的访问和更新操作,因此时间复杂度较高。
-
空间复杂度高:LRU算法需要维护一个队列来记录缓存中各个数据的访问时间,因此空间复杂度较高。
-
对冷启动不友好:当缓存刚启动时,因为缓存中没有任何数据,所以LRU算法无法正确预测哪些数据会被频繁访问,从而导致缓存命中率低下。
4.举例介绍
假设有一个缓存大小为4的LRU缓存,初始状态为空。现在依次访问以下数据:1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5,下面是替换过程:
第一次访问数据1,缓存中插入数据1,列表变为[1]。
第二次访问数据2,缓存中插入数据2,列表变为[1, 2]。
第三次访问数据3,缓存中插入数据3,列表变为[1, 2, 3]。
第四次访问数据4,缓存中插入数据4,列表变为[1, 2, 3, 4]。
第五次访问数据1,数据1已经存在于缓存中,移动数据1到队尾,列表变为[2, 3, 4, 1]。
第六次访问数据2,数据2已经存在于缓存中,移动数据2到队尾,列表变为[3, 4, 1, 2]。
第七次访问数据5,缓存已满,淘汰最近最少使用的数据3,插入数据5,列表变为[4, 1, 2, 5]。
第八次访问数据1,数据1已经存在于缓存中,移动数据1到队尾,列表变为[4, 2, 5, 1]。
第九次访问数据2,数据2已经存在于缓存中,移动数据2到队尾,列表变为[4, 5, 1, 2]。
第十次访问数据3,缓存已满,淘汰最近最少使用的数据4,插入数据3,列表变为[5,1, 2, 3]。
第十一次访问数据4,缓存已满,淘汰最近最少使用的数据5,插入数据4,列表变为[1, 2, 3, 4]。
第十二次访问数据5,缓存已满,淘汰最近最少使用的数据1,插入数据5,列表变为[2,3, 4, 5]。
最终缓存中的数据为[2,3,4, 5]。
三.FIFO
1.算法原理
按照任务到达的先后顺序来进行调度,即先到达的任务先执行,后到达的任务后执行。
2.编程思路
无。(太简单了,不解释)
3.优缺点
1.优点:
-
实现简单:FIFO算法是一种非常简单的调度算法,容易实现和理解。
-
公平性好:由于FIFO算法按照任务到达的先后顺序进行调度,因此每个任务都能够获得公平的CPU时间片。
-
适用范围广:FIFO算法适用于多种场景,如进程调度、线程调度、存储器管理等。
2.缺点:
-
不考虑任务的执行时间:FIFO算法只考虑了任务到达的时间,而没有考虑任务的执行时间。如果一个任务需要运行的时间较长,就会导致其他任务等待的时间变长。
-
容易产生“饥饿”现象:长时间运行的任务可能会占用大量的CPU时间片,导致其他任务无法及时得到响应,从而出现“饥饿”现象。
-
低效率:由于FIFO算法不考虑任务的执行时间,因此可能会导致一些短时间任务等待较长时间,从而降低系统的执行效率。
4.举例介绍
假设有一个缓存大小为4的FIFO缓存,初始状态为空。现在依次访问1,2,3,4,1,2,5,1,2,3,4,5:
假设初始状态为空,则依次访问1,2,3,4时,缓存中的状态为:1 2 3 4
当访问1时,因为1已经在缓存中了,所以不需要替换任何数据。同理,当访问2、3、4时,也不需要进行数据替换。
接下来访问1,由于1已经在缓存中了,所以不需要做任何替换操作。访问2时同理,缓存状态仍为1 2 3 4。
当访问5时,由于5没有在缓存中,需要替换掉最早进入缓存的数据,即1。此时缓存状态为2 3 4 5。
接下来依次访问1、2、3、4时,都不需要进行数据替换,缓存状态保持不变为2 3 4 5。
最后再访问5时,因为5已经在缓存中了,所以不需要进行任何替换操作,缓存状态为2 3 4 5。
因此,FIFO缓存的替换过程就是:1、2、3、4不断加入缓存中,当访问到已经在缓存中的数据时不做替换操作;当访问到不在缓存中的数据时,替换掉最早进入缓存的数据。
四.LFU
1.算法原理
根据数据的访问频率来淘汰缓存中不常用的数据,从而保留常用的数据。(一般要结合其他算法使用)
2.编程思路
LFU算法维护了一个缓存页面列表,其中每个页面都有一个计数器,指示该页面被访问的次数。当需要替换页面时,LFU算法选择计数器值最小的页面进行替换。如果有多个页面具有相同的最小计数器值,则选择最久未被访问的页面进行替换。
LFU算法中的计数器可以是简单的整数计数器,也可以是其他类型的计数器,例如时间戳或加权计数器,以反映不同应用程序中数据的访问模式。
3.优缺点
1.优点:
-
高效地利用缓存:LFU算法能够高效地利用缓存空间,保留经常使用的数据,从而提高缓存命中率。
-
精确地反映数据访问模式:LFU算法可以精确地反映数据的访问模式,对于经常被访问的数据能够得到更好地保护,对于不常访问的数据能够及时淘汰。
-
可自适应地调整计数器:LFU算法可以根据实际情况自适应地调整计数器的值,以适应不同应用程序中数据的访问模式。
2.缺点:
-
算法实现复杂:LFU算法的实现比较复杂,需要维护一个缓存页面列表和对应的计数器,同时需要对计数器进行频繁的更新操作。
-
对突发性访问不友好:LFU算法主要依赖于长期的访问模式,对于突发性访问的数据处理能力不强。
-
对新数据不友好:当缓存中没有任何数据时,LFU算法无法判断数据的访问频率,因此可能导致缓存命中率较低。
4.举例介绍
假设缓存大小为4,初始状态为空。按照题目中的访问序列,依次访问每个数据,并记录缓存的替换过程:
- 访问数据1,缓存为空,将数据1加入缓存中:[1]
- 访问数据2,数据2不在缓存中,将其加入缓存:[1, 2]
- 访问数据3,数据3不在缓存中,将其加入缓存:[1, 2, 3]
- 访问数据4,数据4不在缓存中,将其加入缓存:[1, 2, 3, 4]
- 访问数据1,数据1已经在缓存中,更新其计数器:[2, 3, 4, 1]
- 访问数据2,数据2已经在缓存中,更新其计数器:[3, 4, 1, 2]
- 访问数据5,数据5不在缓存中,缓存已满,选取计数器最小的数据进行替换,得到:[3, 1, 2, 5]
- 访问数据1,数据1已经在缓存中,更新其计数器:[3, 2, 5, 1]
- 访问数据2,数据2已经在缓存中,更新其计数器:[3, 5, 1, 2]
- 访问数据3,数据3已经在缓存中,更新其计数器:[5, 1, 2, 3]
- 访问数据4,数据4不在缓存中,缓存已满,选取计数器最小的数据进行替换,得到:[1, 2, 3, 4]
- 访问数据5,数据5已经在缓存中,更新其计数器:[1, 2, 4, 5]
五.随机替换算法
1.基本认知
随机替换算法是一种常见的缓存替换算法,也称为RAND算法或RR(Random Replacement)算法。它的基本思想是,在缓存满时,随机选择一个块进行替换,从而避免了FIFO、LRU等算法中可能出现的“饥饿”和“抖动”等问题。
随机替换算法没有考虑数据使用的时间顺序和频率,因此不能保证命中率,但是由于其简单性和低开销特点,在某些情况下表现优秀。例如,当访问没有局部性或者缓存容量大时,随机替换算法能够表现得比其他算法更好。
但是需要注意的是,由于随机替换算法没有考虑数据使用的时间顺序和频率,因此可能会出现缓存替换后再次访问相同数据时仍然没有命中的情况,从而降低系统性能。由于这个问题的存在,随机替换算法在实际应用中并不常用,常常被其他算法所取代。
2.举例介绍
以下是一个随机替换算法的简单举例:
假设有4个缓存块,初始状态为空。现在依次访问10个数据块,它们的编号分别为:1、2、3、4、5、6、7、8、9、10。使用随机替换算法对它们进行缓存替换。
首先访问1,由于缓存为空,把1放入第一个缓存块。接下来访问2、3、4时,也都可以直接放入缓存中。
当访问到5时,由于缓存块已经满了,需要随机选择一个块进行替换。假设此时随机选择了第三个缓存块,将其替换成5。此时缓存状态为1 2 5 4。
接着访问6、7、8、9时,也可以直接放入缓存中。当访问到10时,由于缓存块已经满了,需要再次随机选择一个块进行替换。假设此时随机选择了第二个缓存块,将其替换成10。此时缓存状态为1 10 5 4。
整个访问过程结束后,缓存中的数据为1、10、5、4。在这个例子中,随机替换算法按照随机顺序替换缓存块,能够保证每个缓存块的使用次数相对平均,但是也可能会出现替换掉最近使用的数据块的情况。因此,在实际应用中需要根据具体情况进行综合考虑选择适当的缓存替换算法。
六.写策略
常见的Cache写策略有:
- Write-through策略:在数据写入缓存后,会立即将数据写回到主存中。这种策略可以保证数据的一致性,但是会增加主存的访问次数,降低系统性能。
- Write-back策略:在数据写入缓存后,并不立即将数据写回到主存中,而是等到缓存块被替换出去时再写回主存。这种策略可以减少主存访问次数,提高系统性能,但是可能会出现数据的不一致情况。为了避免数据的不一致,需要使用一些额外的技术,如脏位标记、写缓冲区等。
- Write-around策略:在数据写入时,直接将数据写入主存,而不是写入缓存。这种策略可以避免缓存污染问题,但是会增加主存的访问次数。
- Write-invalidate策略:当一个缓存块被修改时,需要将其它缓存中相同的块标记为无效。这种策略可以保证数据的一致性,但是会增加总线上的通信量和延迟。
选择哪种Cache写策略,需要考虑具体的应用场景和需求。例如,对于读多写少的应用,Write-through策略比较适合;对于写多读少的应用,Write-back策略比较适合。同时,还需要考虑系统的可靠性和性能要求等因素,综合选择最优的Cache写策略。
补充:本次的替换策略是基于计算机组成原理这门课的存储系统来介绍的,和操作系统中的页面调度算法,二者在原理上一样,具体操作不一样,具体情况具体分析。