主頁 > 後端開發 > 記憶體淘汰策略|頁面置換演算法對比總結

記憶體淘汰策略|頁面置換演算法對比總結

2023-04-07 07:33:06 後端開發

在學習【作業系統】 【MySQL】【Redis】后,發現其都有一些快取淘汰的策略,因此一篇小文章總結一下,

目前還沒著筆,初略一想MySQL和作業系統應該都是使用的年輕代和老生代的改進策略,而Redis使用的是隨機抽的策略,

MySQL

MySQL中存在一個記憶體快取池,Buffer Pool,里面存在著控制塊和快取的資料頁(當然還有些其他快取,比如:鎖資訊、undo頁等),

下面的LRU限制在快取的資料頁當中(控制塊等應該也是不會淘汰的)

有了快取池之后

  • 當讀取資料時,如果資料存在于 Buffer Pool 中,客戶端就會直接讀取 Buffer Pool 中的資料,否則再去磁盤中讀取,
  • 當修改資料時,首先是修改 Buffer Pool 中資料所在的頁,然后將其頁設定為臟頁,最后由后臺執行緒將臟頁寫入到磁盤,

MySQL會直接操作快取池中的資料,然后再重繪到磁盤中,
Buffer Pool中MySQL還會維護三個鏈表,分別為:LRU鏈表,dirty page 鏈表與free page鏈表,三個鏈表存在分別的意義為:

  • LRU鏈表:記憶體是有限的,不能無限讀入資料,此LRU鏈表key方便決定要淘汰哪些頁面,---也正是本文的核心,記憶體淘汰策略,
  • dirty page 鏈表:為了方便確認哪些資料頁是臟頁,要重繪入磁盤,
  • free page鏈表:為了方便確認哪些頁還沒有讀入資料,

Pasted image 20230404203815.png

由三個鏈表的功能可以知道,最開始free list是滿的(資料還沒有讀入),隨后會越來越少,

Buffer Pool 的大小是有限的,對于一些頻繁訪問的資料我們希望可以一直留在 Buffer Pool 中,而一些很少訪問的資料希望可以在某些時機可以淘汰掉,從而保證 Buffer Pool 不會因為滿了而導致無法再快取新的資料,同時還能保證常用資料留在 Buffer Pool 中,
要實作這個,最容易想到的就是 LRU(Least recently used)演算法,
該演算法的思路是,鏈表頭部的節點是最近使用的,而鏈表末尾的節點是最久沒被使用的,那么,當空間不夠了,就淘汰最久沒被使用的節點,從而騰出空間,
簡單的 LRU 演算法的實作思路是這樣的:

  • 當訪問的頁在 Buffer Pool 里,就直接把該頁對應的 LRU 鏈表節點移動到鏈表的頭部,
  • 當訪問的頁不在 Buffer Pool 里,除了要把頁放入到 LRU 鏈表的頭部,還要淘汰 LRU 鏈表末尾的節點,

簡單的LRU演算法會有兩個問題:預讀失效快取污染

MySQL解決這兩個問題的基本思路:

  1. 解決預讀失效:將快取區分為新生代和老生代,預讀的資料讀入老生代(優先淘汰),如果真正使用了才會加入新生代,
  2. 解決快取污染:由于預讀的資料只要使用了就加入新生代,即使用一次就加入新生代,后面就算不用了新生代也全部被這些只用了一次的資料污染了,因此解決方案就是提高進入新生代的代價,

具體來說,為了解決快取污染:MySQL 是這樣做的,進入到 young 區域條件增加了一個停留在 **old** 區域的時間判斷
具體是這樣做的,在對某個處在 old 區域的快取頁進行第一次訪問時,就在它對應的控制塊中記錄下來這個訪問時間:

  • 如果后續的訪問時間與第一次訪問的時間在某個時間間隔內,那么該快取頁就不會被從 old 區域移動到 young 區域的頭部
  • 如果后續的訪問時間與第一次訪問的時間不在某個時間間隔內,那么該快取頁移動到 young 區域的頭部

這個間隔時間是由 innodb_old_blocks_time 控制的,默認是 1000 ms,
也就說,只有同時滿足「被訪問」與「在 old 區域停留時間超過 1 秒」兩個條件,才會被插入到 young 區域頭部,這樣就解決了 Buffer Pool 污染的問題 ,
另外,MySQL 針對 young 區域其實做了一個優化,為了防止 young 區域節點頻繁移動到頭部,young 區域前面 1/4 被訪問不會移動到鏈表頭部,只有后面的 3/4被訪問了才會,

這個優化非常有意思,因為越靠前的區域就是越熱點的資料,可能來回訪問就是那幾個熱點資料,因此特別熱點的資料訪問就不用來回移動了hhh,

作業系統

參考:

當 CPU 訪問的頁面不在物理記憶體時,便會產生一個缺頁中斷,請求作業系統將所缺頁調入到物理記憶體,
這個將所缺頁調入到物理記憶體的程序也會涉及到記憶體淘汰策略,

不同的是作業系統淘汰的是頁表,而MySQL淘汰的是資料頁,其實本質上都是一樣的hhh,
所缺頁調入到物理記憶體的步驟大概為:

  1. 首先查找頁表,找對應的頁表項,如果頁表項中的狀態位為「無效的」,就觸發缺頁中斷,
  2. 去磁盤找對應頁面在磁盤中的位置,讀取出來,
  3. 準備換入:物理記憶體中找空閑頁,有就換入,沒有就自然涉及了記憶體頁面置換的一些策略了,
  4. 將頁表項中的狀態位設定為[有效的],重新執行對應的觸發缺頁中斷的代碼,

image.png

記憶體淘汰策略演算法目標則是,盡可能減少頁面的換入換出的次數,常見的頁面置換演算法有如下幾種:

  • 最佳頁面置換演算法(OPT):這是理論上的最優演算法,因為未來不可知,所以是理論上的,它是一個頁面置換演算法的上限,
  • 先進先出置換演算法(FIFO):思想就是先入先出佇列,效果差,
  • 最近最久未使用的置換演算法(LRU):近似最優置換演算法,最優置換演算法是通過「未來」的使用情況來推測要淘汰的頁面,而 LRU 則是通過「歷史」的使用情況來推測要淘汰的頁面,使用LRU就要維護一個LRU鏈表(后面這幾句都可以在MySQL的記憶體淘汰策略中有所體現),在每次訪問記憶體時都必須要更新「整個鏈表」,LRU 雖然看上去不錯,但是由于開銷比較大,實際應用中比較少使用,

MySQL用的是LRU,效果好,但是代價也是比較大的,

  • 時鐘頁面置換演算法(Lock):優化置換的次數,也能方便實作兩者兼得,它跟 LRU 近似,又是對 FIFO 的一種改進,

演算法思想為:把所有的頁面都保存在一個類似鐘面的「環形鏈表」中,一個表針指向最老的頁面,
當發生缺頁中斷時,演算法首先檢查表針指向的頁面:遇到1則變為0,然后繼續往下,知道遇到0就替換掉這個頁面,

感覺是相對于FIFO演算法,每個頁面多了一次的選擇機會,

  • 最不常用置換演算法(LFU):當發生缺頁中斷時,選擇「訪問次數」最少的那個頁面,并將其淘汰,(感覺演算法名字不太貼切),具體實作:對每個頁面設定一個「訪問計數器」,每當一個頁面被訪問時,該頁面的訪問計數器就累加 1,在發生缺頁中斷時,淘汰計數器值最小的那個頁面,

要增加一個計數器來實作,這個硬體成本是比較高的,另外如果要對這個計數器查找哪個頁面訪問次數最小,查找鏈表本身,如果鏈表長度很大,是非常耗時的,效率不高,
但還有個問題,LFU 演算法只考慮了頻率問題,沒考慮時間的問題,比如有些頁面在過去時間里訪問的頻率很高,但是現在已經沒有訪問了,而當前頻繁訪問的頁面由于沒有這些頁面訪問的次數高,在發生缺頁中斷時,就會可能會誤傷當前剛開始頻繁訪問,但訪問次數還不高的頁面,
那這個問題的解決的辦法還是有的,可以定期減少訪問的次數,比如當發生時間中斷時,把過去時間訪問的頁面的訪問次數除以 2,也就說,隨著時間的流失,以前的高訪問次數的頁面會慢慢減少,相當于加大了被置換的概率,

Redis

參考:

Redis 記憶體淘汰策略共有八種,這八種策略大體分為「不進行資料淘汰」和「進行資料淘汰」兩類策略,
image.png
1、不進行資料淘汰的策略
noeviction(Redis3.0之后,默認的記憶體淘汰策略) :它表示當運行記憶體超過最大設定記憶體時,不淘汰任何資料,這時如果有新的數據寫入,則會觸發 OOM,但是如果沒用資料寫入的話,只是單純的查詢或者洗掉操作的話,還是可以正常作業,
2、進行資料淘汰的策略
針對「進行資料淘汰」這一類策略,又可以細分為「在設定了過期時間的資料中進行淘汰」和「在所有資料范圍內進行淘汰」這兩類策略,
在設定了過期時間的資料中進行淘汰:

  • volatile-random:隨機淘汰設定了過期時間的任意鍵值;
  • volatile-ttl:優先淘汰更早過期的鍵值,
  • volatile-lru(Redis3.0 之前,默認的記憶體淘汰策略):淘汰所有設定了過期時間的鍵值中,最久未使用的鍵值;
  • volatile-lfu(Redis 4.0 后新增的記憶體淘汰策略):淘汰所有設定了過期時間的鍵值中,最少使用的鍵值;

在所有資料范圍內進行淘汰:

  • allkeys-random:隨機淘汰任意鍵值;
  • allkeys-lru:淘汰整個鍵值中最久未使用的鍵值;
  • allkeys-lfu(Redis 4.0 后新增的記憶體淘汰策略):淘汰整個鍵值中最少使用的鍵值

說白了就是LRULFU演算法,

Redis 是如何實作 LRU 演算法的?

簡單的LRU演算法存在什么問題?

MySQL中考慮的是LRU演算法帶來的預讀失效和Buffer Pool污染的問題(簡單LRU演算法效果不好),而Redis是從LRU演算法維護成本來考慮的,

  • 需要用鏈表管理所有的快取資料,這會帶來額外的空間開銷;
  • 當有資料被訪問時,需要在鏈表上把該資料移動到頭端,如果有大量資料被訪問,就會帶來很多鏈表移動操作,會很耗時,進而會降低 Redis 快取性能,

Redis 實作的是一種近似 LRU 演算法,目的是為了更好的節約記憶體,它的實作方式是在 Redis 的物件結構體中添加一個額外的欄位,用于記錄此資料的最后一次訪問時間
當 Redis 進行記憶體淘汰時,會使用隨機采樣的方式來淘汰資料,它是隨機取 5 個值(此值可配置),然后淘汰最久沒有使用的那個

這樣實作就自然沒有了鏈表的開銷和移動鏈表節點的開銷了,但是多了一個額外欄位(記錄最后一次訪問時間)的開銷,

Redis 是如何實作 LFU 演算法的?

可以在上文中看到作業系統實作LFU演算法的問題有:1.加一個訪問計數器有硬體成本 2.雖叫LFU,但是只考慮了次數,而沒有考慮時間(頻率),

在LFU演算法中,Redis實作相比于作業系統實作更好,真正的考慮了訪問頻率這個問題,
可以思考一下,為了考慮訪問頻率,我們至少需要哪些值,

  1. 當前的訪問頻率的值肯定是要記錄的,logc(Logistic Counter)
  2. 上一次的訪問時間,因為訪問時間間隔不同,訪問頻率的更新值就不同,ldt(Last Decrement Time)

在實作LRU演算法的時候,不是多了一個維護上一次訪問時間的欄位嗎?在LFU演算法中肯定不能浪費呀,也是重新用到了這個欄位,具體為:
在 LRU 演算法中,Redis 物件頭的 24 bits 的 lru 欄位是用來記錄 key 的訪問時間戳,因此在 LRU 模式下,Redis可以根據物件頭中的 lru 欄位記錄的值,來比較最后一次 key 的訪問時間長,從而淘汰最久未被使用的 key,
在 LFU 演算法中,Redis物件頭的 24 bits 的 lru 欄位被分成兩段來存盤,高 16bit 存盤 ldt(Last Decrement Time),低 8bit 存盤 logc(Logistic Counter),
image.png

  • ldt 是用來記錄 key 的訪問時間戳;
  • logc 是用來記錄 key 的訪問頻次,它的值越小表示使用頻率越低,越容易淘汰,每個新加入的 key 的logc 初始值為 5, ---注意是訪問評次,而不是簡單的訪問次數,

本文由博客一文多發平臺 OpenWrite 發布!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549289.html

標籤:其他

上一篇:萬字詳解 | Java 流式編程

下一篇:生產事故-記一次特殊的OOM排查

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more