CPU快取記憶體是什麼?讓CPU不必苦等記憶體的原理
快取記憶體是和CPU放在同一顆晶片上的小而快的儲存空間。它把主記憶體的資料連同附近的部分一起複製過來,之後就從這裡讀取。程式常常接連使用同一筆資料或附近的資料,所以大多數讀取都能在快取完成,CPU等待記憶體的時間會大幅減少。
這篇文章可以了解
- CPU「等待」記憶體是怎麼回事,差距有多大
- 小小的快取為什麼很有效(局部性),以及每次搬64位元組的原理
- 命中率與平均存取時間的計算(考試也常出的觀念)
- L1、L2、L3的差別,放置位置的決定方式,以及寫入的方式
- 瀏覽器快取和CDN也是同樣的想法
先備知識:只要知道CPU是「執行指令的零件」,記憶體是「放置工作中資料的地方」就夠了。可以接在「CPU是什麼?」「記憶體和儲存空間有什麼不同?」之後閱讀。
生活中的例子
電腦用CPU的規格表上,會列出「L2快取 16 MB」「L3快取 64 MB」這樣的數字。這是AMD在2024年推出的Ryzen 9 9950X的例子。另一方面,瀏覽器的設定畫面裡,有「清除快取」的按鈕。
兩者都是同一個想法:把常用東西的副本,放在使用的地方附近。這篇文章以CPU的快取為主來說明,最後也會談到CPU以外的快取。
CPU其實在「等」記憶體
CPU一邊從主記憶體(以下簡稱記憶體)讀取程式的指令和資料,一邊工作。記憶體使用的是一種叫DRAM的晶片。問題在於,和CPU的速度相比,從記憶體讀取非常慢。
從向記憶體要求到資料送達,等待時間依產品而定,大約是數十到100奈秒(1奈秒是十億分之一秒)。另一方面,以4 GHz運作的CPU,推進處理的訊號(時脈)間隔是0.25奈秒。等記憶體100奈秒的期間,時脈就過了400次。
對於每個時脈能推進好幾道指令的CPU來說,這是很長的停頓。而且多年來,記憶體等待時間的縮短,一直跟不上CPU速度的成長。
因此,CPU會把記憶體資料的副本,放在自己身邊小而快的儲存空間裡。這就是快取記憶體。現在的快取和CPU在同一顆晶片上,最近的一層大約1奈秒就能讀到。
快取使用的是一種叫SRAM的電路。多數用6個電晶體記住1位元,讀取比DRAM快。但它比較占面積,所以無法做得很大(「DRAM與SRAM的差異」)。
小小的空間就夠用的原因:局部性
快取的容量只有記憶體的數百分之一以下。它還是有用,是因為程式使用資料的順序有固定的習性。這種習性稱為「局部性」,分成2種。
第1種是,用過一次的資料,很快又會再用到。這稱為時間局部性。例如,重複執行的迴圈裡,會一再使用計算次數的變數和同樣的指令。
第2種是,用到某筆資料之後,很快也會用到它附近的資料。這稱為空間局部性。程式的指令在記憶體裡依序排列,通常也依序執行。就像讀完書的一頁,接下來通常會讀下一頁。
每次搬64位元組:快取行
為了善用空間局部性,快取不只搬來要求的資料,也會把周圍的資料一起從記憶體搬來。這個搬運的單位稱為快取行(cache line)。在x86的CPU和許多Arm的CPU上,1個快取行是64位元組(在Apple的M系列上,macOS回報的是128位元組)。
例如,假設記憶體裡排著一長串8位元組的數。1個快取行可以裝8個這樣的數。CPU要第1個數時,快取裡還沒有,所以要到記憶體去拿。這時,旁邊的7個數也會一起被複製過來。
快取裡有要找的資料稱為「命中」,沒有則稱為「未命中」。在上面的例子中依序讀8個數,只有第1次未命中,其餘7次都命中。即使是第一次讀的資料,8次中也有7次,也就是87.5%命中。
實際的CPU還會預先讀取接下來可能用到的快取行(預取),這裡先省略。
命中率與平均存取時間
讀取當中命中的比例,稱為「命中率」。讀100次有95次命中,命中率就是95%,其餘的5%是「未命中率」。
快取的效果,要用每次讀取所花時間的平均來比較。把命中和未命中時的時間,各自乘上發生的比例再相加。資訊類考試常用的有效存取時間公式如下。
有效存取時間 = 命中率 × 快取的時間 +(1 − 命中率)× 記憶體的時間
實際算算看
假設快取讀取要1奈秒,記憶體要100奈秒,命中率是95%。
- 命中的部分:0.95 × 1奈秒 = 0.95奈秒
- 未命中的部分:0.05 × 100奈秒 = 5奈秒
- 平均:0.95 + 5 = 5.95奈秒
沒有快取的話每次都要100奈秒,所以平均縮短到大約17分之1。不過,5.95奈秒當中有5奈秒,來自僅僅5%的未命中。
教科書裡還有另一種形式,稱為平均記憶體存取時間(AMAT)。它是「命中時間+未命中率×未命中時多花的時間」。未命中時,是在查快取的1奈秒之外,再加上記憶體的100奈秒。上面的例子是1+0.05×100=6奈秒。
0.05奈秒的差距,來自未命中的時間是否包含查快取的部分。考試時,要依照題目給的公式和前提來計算。想換數字試試看,可以用「平均存取時間計算器」。
L1、L2、L3:依速度和大小分層
快取做得越大,命中率越高。但儲存空間越大,找出並取出要的資料就越花時間。
所以快取也分成好幾層。離運算電路最近、最小最快的一層,稱為「L1快取」。接著是L2、L3,編號越大,容量越大,速度也越慢。CPU從L1開始依序尋找,哪一層都沒有,才到記憶體去拿。
以2024年推出的電腦用高階CPU,來看看大小的參考值。分層的方式和名稱,依產品而不同。
| 層 | 配置方式 | 大小的例子(2024年的產品) |
|---|---|---|
| L1 | 每個核心各有一個,分成指令用和資料用 | 每個核心數十到數百KB(Ryzen 9 9950X為80 KB) |
| L2 | 每個核心各有一個,或每幾個核心一組共用 | 每個核心1 MB以上(9950X合計16 MB,Core Ultra 9 285K合計40 MB) |
| L3 | 多個核心共用 | 數十MB(9950X為64 MB,285K為36 MB) |
9950X的L1,是把規格上的合計1,280 KB除以16個核心的值。各層的差異,整理在「L1、L2、L3快取的差異」的表格中。
放在哪裡:直接映射與集合關聯
因為快取很小,必須決定記憶體的哪個快取行,要放在快取的哪個位置。
最簡單的方法,是由記憶體的位址,直接決定唯一的1個位置。例如快取有4個位置,就用快取行的編號除以4的餘數來決定。這稱為直接映射方式。只要查1個位置,所以很快,電路也簡單。
缺點是,被分配到同一位置的2個快取行輪流使用時,會互相把對方擠出去。下圖的快取行0和4就是例子,即使其他位置空著,也會一直未命中。
因此,現在多數CPU會把位置分成稱為「集合」的組。位址只決定集合,快取行可以放在集合中幾個位置的任一處。這稱為集合關聯方式(也稱組相聯)。集合裡的位置會同時檢查,所以尋找的時間不太會增加。
也有可以放在任何位置的方式(完全關聯方式),但同時檢查所有位置的電路會變得很大。3種方式的比較,請看「直接映射、組相聯、全相聯的差異」。
滿了怎麼辦:選出要逐出的快取行
集合裡滿了的話,為了放進新的快取行,就要逐出其中1個。代表性的做法,是逐出最久沒被使用的那一個,稱為LRU(最近最少使用)。從時間局部性來看,一段時間沒用的資料,之後應該也不太會用到。實際的CPU很難精確記住順序,所以也常用接近LRU的簡化方式。
作業系統把記憶體的內容移到儲存空間時,也是用同樣的想法(「作業系統(OS)是什麼?」)。
寫入時:寫穿與寫回
只有讀取的話,快取裡的副本和記憶體裡的原本是一樣的。但CPU一改寫資料,副本和原本就不一致了。依何時反映到記憶體,大致分成2種方式。
第1種是寫入快取的同時,也寫入記憶體。這稱為寫穿(write-through)方式。記憶體永遠是最新的,所以機制很簡單。但每次寫入都要和慢速的記憶體往來。
第2種是先只改寫快取,稱為「寫回(write-back)方式」。改寫過的快取行會加上記號,等到它被逐出時,再寫回記憶體。同一個位置改寫再多次,寫入記憶體也只有逐出時的1次。但有一段時間,記憶體的內容是舊的,所以管理比較複雜。
現在電腦用CPU的資料快取,一般採用寫回。差異整理在「寫穿與寫回的差異」的表格中。
依序讀取的程式比較快的原因
了解快取行的原理,就能明白為什麼同樣的計算,寫法不同速度也會不同。舉例來說,要把1萬列×1萬欄的表格(二維陣列)裡的數全部加起來。每個數是8位元組。
像C語言這樣把表格一列一列接著排的語言,會依第1列從左到右、再到第2列的順序排在記憶體裡。1列是8萬位元組,整個表格是800 MB,比任何一層快取都大得多。
如果沿著每一列從左到右相加,就是依記憶體排列的順序讀取。1個快取行可以拿到8個數,所以每8次才未命中1次。
另一方面,如果沿著每一欄從上到下相加,每讀1個就要跳到8萬位元組之後,每次都是不同的快取行。在最小的L1裡,幾乎都是未命中。搬來的快取行裡其餘的7個數,等到下一欄要用時,早已被逐出L1。相加的次數和答案都一樣,花的時間卻差很多。
這裡把情況簡化了。實際的差距,會因CPU和表格的大小而改變。即便如此,「依記憶體排列的順序讀取」仍是寫出快速程式的基本原則。「陣列與鏈結串列的差異」也和這個性質有關。
有多個核心時
現在的CPU有多個核心,各自擁有自己的L1和L2。這樣一來,2個核心可能持有同一筆資料的副本。一方改寫後,另一方的副本就舊了。因此,CPU會用電路自動通知,例如讓其他核心的副本失效。這種讓每個核心看到相同值的機制,稱為快取一致性。
CPU以外的快取
「把常用東西的副本放在附近」的想法,在CPU以外也有使用。例如,瀏覽器會把讀過的網頁圖片等存在裝置裡,下次不用到遠方的伺服器拿,直接用手邊的副本。這就是瀏覽器快取。
從名稱查出IP位址的DNS,也會用快取把答案記住一段時間(「DNS是什麼?」)。提供影片和圖片的服務,會把副本放在世界各地的伺服器,從離使用者近的伺服器送出。這種機制稱為CDN(內容傳遞網路)。
副本會變舊這個問題也一樣。不同的是處理方式和速度。CPU由電路以奈秒為單位讓副本保持一致。瀏覽器和CDN則用軟體決定副本可以使用的期限,減少以毫秒(千分之一秒)為單位的通訊等待。
常見的誤解
誤解:快取越大的CPU,一定越快
實際上,效果依程式而有很大的差別。如果要用的資料已經放得下,再加大快取,命中率也幾乎不會提高。快取變大,讀取也會更花時間,這就是最近的L1要保持小容量的原因。AMD也把L3有96 MB的Ryzen 7 9800X3D,主要定位為遊戲用途來銷售。
誤解:快取是裝在主機板上的另一顆記憶體晶片
實際上,現在的CPU,快取通常和CPU在同一顆晶片上。像9800X3D這樣把快取晶片疊上去的產品,也還是在CPU這1個零件裡面。這個誤解來自以前的電腦。1989年推出的Intel 486,晶片上有8 KB的快取,還可以在主機板上加裝第2層快取。現在插在主機板上的記憶體,是主記憶體。
誤解:命中率有99%,就不必在意未命中
實際上,1%的未命中,有時就占了平均時間的一半。用上面例子的數字計算,99%時的平均是0.99+1=1.99奈秒。其中1奈秒,就是僅僅1%的未命中造成的。命中率降到98%,平均會變成約2.98奈秒,大約拉長為1.5倍。未命中1次的時間是命中1次的100倍,所以1%的差距影響很大。
從數字來看
| 項目 | 數字 | 比較 |
|---|---|---|
| 4 GHz的CPU的1次時脈 | 0.25奈秒 | 1秒40億次 |
| L1快取的讀取 | 約1奈秒 | 幾次時脈 |
| 記憶體(DRAM)的讀取 | 數十到100奈秒 | 100奈秒是400次時脈 |
| 1個快取行的大小 | 64位元組 | 8個8位元組的數(x86和許多Arm) |
| L3快取的例子(2024年) | 36~96 MB | Core Ultra 9 285K、Ryzen 9 9950X、Ryzen 7 9800X3D |
等記憶體1次的期間,時脈會前進數百次。快取就是把大部分這種等待,縮短到幾次時脈的方法。
進一步學習
- 用表格看差異:寫入配置與不寫入配置的差異(寫入未命中時的動作)、MSI、MESI、MOESI的差異(核心之間讓副本一致的步驟)
- 下一步的用語:未命中代價、衝突未命中、容量未命中(未命中的種類)、偽共享(核心之間搶同一個快取行的現象)、TLB(用於位址轉換的快取)
- 記憶體的速度限制AI運算的故事:「AI為什麼要用GPU?」