在計算機系統中，CPU 快取記憶體（，在本文中簡稱快取）是用於減少處理器訪問記憶體所需平均時間的部件。在金字塔式儲存體系中它位於自頂向下的第二層，僅次於 CPU 暫存器。其容量遠小於記憶體，但速度卻可以接近處理器的頻率。 當處理器發出記憶體訪問請求時，會先檢視快取內是否有請求資料。如果存在（命中），則不經訪問記憶體直接返回該資料；如果不存在（失效），則要先把記憶體中的相應資料載入快取，再將其返回處理器。 快取之所以有效，主要是因為程式執行時對記憶體的訪問呈現區域性性（Locality）特徵。這種區域性性既包括空間區域性性（Spatial Locality），也包括時間區域性性（Temporal Locality）。有效利用這種區域性性，快取可以達到極高的命中率。 在處理器看來，快取是一個透明部件。因此，程式設計師通常無法直接干預對快取的操作。但是，確實可以根據快取的特點對程式程式碼實施特定最佳化，從而更好地利用快取。 基本描述 快取的儲存結構 結構上，一個直接對映（Direct Mapped）快取由若干快取塊（Cache Block，或 Cache Line）構成。每個快取塊儲存具有連續記憶體地址的若干個儲存單元。在 32 位計算機上這通常是一個雙字（dword），即四個位元組。因此，每個雙字具有唯一的塊內偏移量。 每個快取塊有一個索引（Index），它一般是記憶體地址的低端部分，但不含塊內偏移和位元組偏移所佔的最低若干位。一個數據總量為 4KB、快取塊大小為 16B 的直接對映快取一共有 256 個快取塊，其索引範圍為 0 到 255。使用一個簡單的移位函式，就可以求得任意記憶體地址對應的快取塊的索引。由於這是一種多對一對映，必須在儲存一段資料的同時標示出這些資料在記憶體中的確切位置。所以每個快取塊都配有一個標籤（Tag）。拼接標籤值和此快取塊的索引，即可求得快取塊的記憶體地址。如果再加上塊內偏移，就能得出任意一塊資料的對應記憶體地址。 因此，在快取大小不變的情況下，快取塊大小和快取塊總數成反比關係。下圖中所示的快取塊來自一個數據總量為 4KB、每個快取塊大小為 16B 的直接對映快取，其標籤長度為 20bits（ 32 log_2 4096 div 16 log_2 16 20）。 此外，每個快取塊還可對應若干標誌位，包括有效位（valid bit）、髒位（dirty bit）、使用位（use bit）等。這些位在保證正確性、排除衝突、最佳化效能等方面起著重要作用。 運作流程 下面簡要描述一個假想的直接對映快取的工作流程。這個快取共有四個快取塊，每個塊 16 位元組，即 4 個字，因此共有 64 位元組儲存空間。使用寫回（Write back）策略以保證資料一致性。 系統啟動時，快取內沒有任何資料。之後，資料逐漸被載入或換出快取。假設在此後某一時間點，快取和記憶體佈局如右圖所示。此時，若處理器執行資料讀取指令，控制邏輯依如下流程： 將地址由高至低劃分為四個部分：標籤、索引、塊內偏移、位元組偏移。其中塊內偏移和位元組偏移各佔兩位，後者在以下操作中不使用。 用索引定位到相應的快取塊。 用標籤嘗試匹配該快取塊的對應標籤值。如果存在這樣的匹配，稱為命中（Hit）；否則稱為未命中（Miss）。 如命中，用塊內偏移將已定位快取塊內的特定資料段取出，送回處理器。 如未命中，先用此塊地址（標籤 索引）從記憶體讀取資料並載入到當前快取塊，再用塊內偏移將位於此塊內的特定資料單元取出，送回處理器。這裡要注意的是，（1）讀入的資料會沖掉之前的內容。為保證資料一致性，必須先將資料塊內的現有內容寫回記憶體。（2）儘管處理器請求的只是一個字，快取仍必須在讀取的時候把整個資料塊都填充滿。（3）快取的讀取是按快取塊大小為邊界對齊的。對於大小為 16 位元組的快取塊，任何因為 0x0000、或 0x0001、或 0x0002、或 0x0003 造成的未命中，都會導致位於記憶體 0x00000x0003 的全部四個字被讀入塊中。 在右圖中，如此時處理器請求的地址在 0x0020 到 0x0023 之間，或在 0x0004 到 0x0007 之間，或在 0x0528 到 0x052B 之間，或在 0x05EC 到 0x05EF 之間，均會命中。其餘地址則全部未命中。 而處理器執行資料寫入指令時，控制邏輯依如下流程： 用索引定位到相應的快取塊。 用標籤嘗試匹配該快取塊的對應標籤值。其結果為命中或未命中。 如命中，用塊內偏移定位此塊內的目標字。然後直接改寫這個字。 如未命中，依系統設計不同可有兩種處理策略，分別稱為按寫分配（Write allocate）和不按寫分配（Nowrite allocate）。如果是按寫分配，則先如處理讀未命中一樣，將未命中資料讀入快取，然後再將資料寫到被讀入的字單元。如果是不按寫分配，則直接將資料寫回記憶體。 組相聯 直接對映 為了便於資料查詢，一般規定記憶體資料只能置於快取的特定區域。對於直接對映快取，每一個記憶體塊地址都可透過模運算對應到一個唯一快取塊上。注意這是一種多對一對映：多個記憶體塊地址須共享一個快取區域。 I A_mbmod N 其中 I 為快取索引，Amb 為記憶體塊地址，N 為快取塊總數。 使用記憶體塊地址而不是記憶體地址是因為快取塊通常包含一組連續的記憶體單元資料。以快取塊為 32 位元組的直接對映快取為例，記憶體地址 Am 到快取索引的計算為 I left A_mdiv 32 right mod N 由於快取位元組數和快取塊數均為 2 的冪，上述運算可以由硬體透過移位極快地完成。 N 路組相聯 直接匹配快取儘管在電路邏輯上十分簡單，但是存在顯著的衝突問題。由於多個不同的記憶體塊僅共享一個快取塊，一旦發生快取失效就必須將快取塊的當前內容清除出去。這種做法不但因為頻繁的更換快取內容造成了大量延遲，而且未能有效利用程式執行期所具有的時間區域性性。 組相聯（Set Associativity）是解決這一問題的主要辦法。使用組相聯的快取把儲存空間組織成多個組，每個組有若干資料塊。透過建立記憶體資料和組索引的對應關係，一個記憶體塊可以被載入到對應組內的任一資料塊上。 以右圖為例， 如使用 2 路組相聯，記憶體地址為 0、8、16、24 的資料均可被置於快取第 0 組中兩個資料塊的任意一個；如果使用 4 路組相聯，記憶體地址為 0、8、16、24 的資料均可被置於快取第 0 組中四個資料塊的任意一個。一般地， I left A_mdiv N_wdiv N_a right mod N 其中，I 為快取索引，Am 為記憶體地址，Nw 為快取塊內字數， Na 為相聯路數， N 為組數。當使用組相聯時，在透過索引定位到對應組之後，必須進一步地與所有快取塊的標籤值進行匹配，以確定查詢是否命中。這在一定程度上增加了電路複雜性，因此會導致查詢速度有所降低。 此外，在不增大快取大小的前提下單純地增加組相聯的路數，將不會改變快取和記憶體的對應比例。再以右圖為例，對於 2 路組相聯，儘管第 0 組內有兩個快取塊，但是該組現在也是記憶體塊 1、9、17、25 的目標塊。 直接匹配可以被認為是單路組相聯。經驗規則表明，在快取小於 128KB 時，欲達到相同失效率，一個雙路組相聯快取僅需相當於直接匹配快取一半的儲存空間 Computer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 C28 頁。 全相聯 組相聯的一個極端是全相聯。這種快取意味著記憶體中的資料塊可以被放置到快取的任意區域。這種相聯完全免去了索引的使用，而直接透過在整個快取空間上匹配標籤進行查詢。 由於這樣的查詢造成的電路延遲最長，因此僅在特殊場合，如快取極小時，才會使用。 置換策略 對於組相聯快取，當一個組的全部快取塊都被佔滿後，如果再次發生快取失效，就必須選擇一個快取塊來替換掉。存在多種策略決定哪個塊被替換。 顯然，最理想的替換塊應當是距下一次被訪問最晚的那個。這種理想策略無法真正實現，但它為設計其他策略提供了方向。 先進先出演算法（FIFO）替換掉進入組內時間最長的快取塊。最久未使用演算法（LRU）則跟蹤各個快取塊的使用狀況，並根據統計比較出哪個塊已經最長時間未被訪問。對於 2 路以上相聯，這個演算法的時間代價會非常高。 對最久未使用演算法的一個近似是非最近使用（NMRU）。這個演算法僅記錄哪一個快取塊是最近被使用的。在替換時，會隨機替換掉任何一個其他的塊。故稱非最近使用。相比於 LRU，這種演算法僅需硬體為每一個快取塊增加一個使用位（use bit）即可。 此外，也可使用純粹的隨機替換法。測試表明完全隨機替換的效能近似於 LRUComputer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 C10 頁。 寫操作 回寫策略 為了和下級儲存（如記憶體）保持資料一致性，就必須把資料更新適時傳播下去。這種傳播透過回寫來完成。一般有兩種回寫策略：寫回（Write back）和寫通（Write through）。 寫回是指，僅當一個快取塊需要被替換回記憶體時，才將其內容寫入記憶體。如果快取命中，則總是不用更新記憶體。為了減少記憶體寫操作，快取塊通常還設有一個髒位（dirty bit），用以標識該塊在被載入之後是否發生過更新。如果一個快取塊在被置換回記憶體之前從未被寫入過，則可以免去回寫操作。 寫回的優點是節省了大量的寫操作。這主要是因為，對一個數據塊內不同單元的更新僅需一次寫操作即可完成。這種記憶體頻寬上的節省進一步降低了能耗，因此頗適用於嵌入式系統。 回寫策略 分配策略 當 時 寫到 寫回 分配 命中 快取 寫回 分配 失效 快取 寫回 非分配 命中 快取 寫回 非分配 失效 記憶體 寫通 分配 命中 快取和記憶體 寫通 分配 失效 快取和記憶體 寫通 非分配 命中 快取和記憶體 寫通 非分配 失效 記憶體 寫通是指，每當快取接收到寫資料指令，都直接將資料寫回到記憶體。如果此資料地址也在快取中，則必須同時更新快取。由於這種設計會引發造成大量寫記憶體操作，有必要設定一個緩衝來減少硬體衝突。這個緩衝稱作寫緩衝器（Write buffer），通常不超過 4 個快取 Block 大小。不過，出於同樣的目的，寫緩衝器也可以用於寫回型快取。 寫通較寫回易於實現，並且能更簡單地維持資料一致性。 按寫分配與不按寫分配 當發生寫失效時，快取可有兩種處理策略，分別稱為按寫分配（Write allocate）和不按寫分配（Nowrite allocate）。 按寫分配是指，先如處理讀失效一樣，將所需資料讀入快取，然後再將資料寫到被讀入的單元。不按寫分配則總是直接將資料寫回記憶體。 設計快取時可以使用回寫策略和分配策略的任意組合。對於不同組合，發生資料寫操作時的行為也有所不同。如右表所示。 地址翻譯 由於計算機程式一般使用虛擬地址，一個必須決定的設計策略是快取的地址標籤及索引是使用虛擬地址還是物理地址。 虛快取 一個簡單的方案就是快取的標籤和索引均使用虛擬地址。這種快取稱為虛快取（virtual cache）。這種快取的優點是僅在快取失效時才需要進行頁面翻譯。由於快取命中率很高，需要翻譯的次數也相對較少。 但是這種技術也存在嚴重的問題。 第一，引入虛擬地址的一個重要原因是在軟體（作業系統）級進行頁面保護，以防止程序間相互侵犯地址空間。由於這種保護是透過頁表和翻譯旁視緩衝器（TLB）中的保護位（protection bit）實現的，直接使用虛擬地址來訪問資料等於繞過了頁面保護。一個解決辦法是在快取失效時檢視 TLB 對應表項的保護位以確定是否可以載入缺失的資料。 第二，由於不同程序使用相同的虛擬地址空間，在切換程序後會出現整個快取都不再對應新程序的有效資料。如果前後兩個程序使用了相同的地址區間，就可能會造成快取命中，卻訪問了錯誤的地址，導致程式錯誤。有兩個解決辦法：（1）程序切換後清空快取。代價過高。（2）使用程序識別符號（PID）作為快取標籤的一部分，以區分不同程序的地址空間。 第三，別名問題（Alias）。由於作業系統可能允許頁面別名，即多個虛擬頁面對映至同一物理頁面，使用虛擬地址做標籤將可能導致一份資料在快取中出現多份複製的情形。這種情況下如果對其中一份複製作出修改，而其他複製沒有同步更新，則資料喪失整合性，導致程式錯誤。有兩個解決辦法：（1）硬體級反別名。當快取載入目標資料時，確認快取內沒有快取塊的標籤是此地址的別名。如果有則不載入，而直接返回別名快取塊內的資料。（2）頁面著色（Page Coloring）。這種技術是由作業系統對頁面別名作出限制，使指向同一頁面的別名頁面具有相同的低端地址。這樣，只要快取的索引範圍足夠小，就能保證在快取中決不會出現來自不同別名頁面的資料。 第四，輸入輸出問題。由於輸入輸出系統通常只使用物理地址，虛快取必須引入一種逆對映技術來實現虛擬地址到物理地址的轉換。 實快取 實快取（physical cache）完全使用物理地址做快取塊的標籤和索引，故地址翻譯必須在訪問快取之前進行。這種傳統方法所以可行的一個重要原因是 TLB 的訪問週期非常短（因為本質上 TLB 也是一個快取），因而可以被納入流水線。 但是，由於地址翻譯發生在快取訪問之前，會比虛快取更加頻繁地造成 TLB。（相比之下，虛快取僅在本身失效的前提下才會訪問 TLB，進而有可能引發 TLB 失效）實快取在執行中存在這樣一種可能：首先觸發了一個 TLB 失效，然後從頁表中更換 TLB 表項（假定頁表中能找到）。然後再重新訪問 TLB，翻譯地址，最後發現數據不在快取中。Computer Organization And Design The HardwareSoftware Interface，第四版，Morgan Koffmann 出版，第 507 頁 虛索引、實標籤快取 一個折中方案是同時使用虛索引和實標籤（virtually indexed physically tagged）。這種快取利用了頁面技術的一個特徵，即虛擬地址和物理地址享有相同的頁內偏移值（page offset）。這樣，可以使用頁內偏移作為快取索引，同時使用物理頁面號作為標籤。這種混合方式的好處在於，其既能有效消除諸如別名引用等純虛快取的固有問題，又可以透過對 TLB 和快取的並行訪問來縮短流水線延遲。 這種技術的一個缺點是，在使用直接匹配快取的前提下，快取大小不能超過頁面大小，否則頁面偏移範圍就不足以覆蓋快取索引範圍。這個弊端可以透過提高組相聯路數來改善。 多級快取 引入動機 介於處理器和記憶體二者之間的快取有兩個天然衝突的效能指標：速度和容積。如果只向處理器看齊而追求速度，則必然要靠減少容積來換取訪問時間；如果只向記憶體看齊而追求容積，則必然以增加處理器的訪問時間為犧牲。這種矛盾促使人們考慮使用多級快取。 在一個兩級快取體系中，一級快取靠近處理器一側，二級快取靠近記憶體一側。當一級快取發生失效時，它向二級快取發出請求。如果請求在二級快取上命中，則資料交還給一級快取；如失效，二級快取進一步向記憶體發出請求。對於三級快取可依此類推。 通常，更接近記憶體的快取有著更大容積，但是速度也更慢。值得注意的是，無論如何，低階快取的區域性命中率總是低於高階快取。這是因為資料的時空區域性性在一級快取上基本上已經利用殆盡。 設計考慮 雖然功能類似，但不同級別的快取在設計和實現上也有不同之處。 儘管一般而言，在儲存體系結構中低階儲存總是包含高階儲存的全部資料，但對於多級快取則未必。相反地，存在一種多級排他性（Multilevel exclusion）的設計。此種設計意指高階快取中的內容和低階快取的內容完全不相交。這樣，如果一個高階快取請求失效，並在次級快取中命中的話，次級快取會將命中資料和高階快取中的一項進行交換，以保證排他性。 多級排他性的好處是在儲存預算有限的前提下可以讓低階快取更多地儲存資料。否則低階快取的大量空間將不得不用於覆蓋高階快取中的資料，這無益於提高低階快取的命中率。 當然，也可以如記憶體對快取般，使用多級包容性（Multilevel inclusion）設計。這種設計的優點是比較容易方便檢視快取和記憶體間的資料一致性，因為僅檢查最低一級快取即可。對於多級排他性快取這種檢查必須在各級上分別進行。這種設計的一個主要缺點是，一旦低階快取由於失效而被更新，就必須相應更新在高階快取上所有對應的資料。因此，通常令各級快取的快取塊大小一致，從而減少低階對高階的不必要更新。 此外，各級快取的寫策略也不相同。對於一個兩級快取系統，一級快取可能會使用寫通來簡化實現，而二級快取使用寫回確保資料一致性。 效能評估 效能評估模型 評估快取的效能通常使用平均記憶體訪問時間（Average Memory Access Time AMAT）這一指標。在一個簡化模型中，該值可依下式求得： AMAT T_hit MR times MP 式中三項的意義： T_hit 為命中時間（Hit Time）：從定位快取塊、經標籤比較並選中，一直到傳回資料所需的時間。 MR 為失效率（Miss Rate）：在特定次數的記憶體訪問中，發生快取失效次數所佔的比重。 MP 為失效代價（Miss Penalty）：從定位快取塊、經標籤比較判定失效，然後再從記憶體中定位資料並載入快取，最後直到把目標資料返回所需的時間。 失效分析 Mark Hill 在對快取失效的情形進行研究後給出了三種快取失效的原因，稱為 3C。 強制失效（Compulsory miss），又稱冷失效（Cold start miss），指地址第一次被引用時的失效。改變快取大小或相聯度都不能影響這類失效。 容量失效（Capacity miss），是指某段資料由於快取已滿而被逐出後，當快取再一次企圖訪問此資料時造成的失效。改變快取塊大小或相聯度都不能影響這類失效。 衝突失效（Conflict miss），是指記憶體中不同的塊被對映到快取中相同的組或塊，導致訪問時產生衝突而失效。也叫 Collision misses 或者 Interference misses。這類失效對於全相聯快取並不存在。 此外，在多處理器系統中，還存在為保證各處理器快取之間的資料一致性而進行資料清空 無效化所造成的失效。這類失效稱為一致失效（Coherency miss）。 最佳化技術 根據 AMAT 的計算式，可以看出最佳化快取可從三個方面入手：一、減少命中時間；二、降低失效率；三、減輕失效代價。此外，增加快取訪問頻寬也能有效降低 AMAT。 存在多種最佳化技術來實現削減三個構成變數對 AMAT 造成的影響。應注意的是，由於快取的內在性質，某些技術可能在減少一個因子的同時，增加了另一個因子。 減少命中時間 虛索引、實標籤快取 理論上，完全使用虛擬地址可以獲得更快的快取訪問速度，因為這樣僅在快取失效時才會進行地址翻譯。但是，如前所述，這種純虛地址快取由於繞開了作業系統對程序訪問地址的軟體控制，會存在不少問題。 為了能接近虛快取的訪問速度，又能避開虛快取帶來的種種問題，引入了所謂虛索引、實標籤快取（virtually indexed physically tagged）。這種結構的快取可以令地址翻譯和快取查詢併發進行，大大加快了快取的訪問速度。詳見地址翻譯一節。 小而簡單的快取 由於電路延遲很大程度上取決於儲存晶片的大小，所以可考慮使用較小容量的快取以保證最短的訪問週期。這麼做的另一個好處是，由於一級快取足夠小，可以把二級快取的全部或部分也整合到 CPU 晶片上，從而減少了二級快取的命中時間。 AMD 從 K6 到 Opteron 連續三代 CPU 的一級快取容量都沒有任何增長（均為 64KB）正是基於這個原因 Computer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 294 頁。 另一方面，考慮使用簡單的快取，如直接匹配快取，也可較組相聯快取減少命中時間。 路預測 所謂路預測（Way prediction），是指在組相聯快取中，跟蹤同一組內不同快取塊的使用情況，然後在訪問到來時，不經比較直接返回預測的快取塊。當然，標籤比較仍然會進行，並且如果發現比較結果不同於預測結果，就會重新送出正確的快取塊。也就是說，錯誤預測會造成一個快取塊長度的延遲。 模擬表明路預測的準確率超過 85 Computer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 295 頁。這種技術非常適合於投機執行（Speculative Execution）處理器，因為這種處理器有完善的機制來保證在投機失敗之後取消已經派發的指令。 追蹤快取 與一般的指令快取儲存靜態連續地址不同，追蹤快取（Trace Cache）儲存的是基於執行歷史的動態地址序列。這實際上是把分支預測的結果用在了快取上。由於只儲存沿某一特定分支路徑才會遇到的指令，這種快取可比傳統快取更節省空間。 追蹤快取的缺點是實現複雜，因為必須設法連續儲存的資料並不會按照 2 的冪次字長對齊。此外，對於不同執行路徑要分開儲存。如果這些執行路徑中存在相同地址的指令，這些指令就只好被分別存到兩個地方。這反而造成了低效的空間利用。 Intel 的 Pentium 4 處理器使用了這一複雜技術。值得一提的是，Pentium 4 追蹤快取儲存的不是從記憶體抓取的原始指令，而是已經過解碼的微操作，從而進一步節省掉了指令解碼上要花的時間。 增加訪問頻寬 快取流水線化 將一級快取併入流水線是一般做法。這種做法可行性在於一級快取的訪問時間通常都極短，可能只有一到數個 CPU 週期。此外，由於 TLB 也是一種快取記憶體硬體，故也可以納入流水線。 非阻塞快取 一般而言，當快取發生失效時，處理器必須停滯（stall），等待快取將資料從次級儲存中讀取出來。 當時，對於跨序執行（Outoforder Execution）處理器，由於多條指令在不同處理單元中併發執行，某一條指令引發的快取失效應該只造成其所在處理單元的停滯，而不影響其他處理單元和指令派發單元繼續流水。因此，有必要設計這樣一種快取，使之能夠在處理快取失效的同時，繼續接受來自處理器的訪問請求。這稱為非阻塞快取（Nonblocking cache）。 降低失效率 使用更大的資料塊 使用大資料塊有助於利用空間區域性性降低失效率，但其代價是更高的失效代價。這是因為，一旦失效，就必須把整個資料塊都重新填滿。 使用更大的快取 單純增大快取的容量也是降低失效率的一個辦法。不過顯然這也增大了命中時間。 高組相聯快取 使用多路組相聯可以減少衝突失效。但其後果是快取電路邏輯複雜化，故增大了命中時間。 編譯器最佳化 存在多種編譯器最佳化技術來間接影響快取的使用模式。下面僅舉幾例，且均假定編譯器採用行主序（Rowmajor order）儲存陣列： 1 迴圈交換（Loop Interchange） 考慮一個對二維陣列 a 1005000 的迴圈處理 a 1005000 初始化 for j 0 j 5000 j j 1 for i 0 i 100 i i 1 a ij 2 a ij 如果原始碼的外迴圈遍歷行，而內迴圈遍歷列，則總是會造成大量的快取失效。這是因為當失效時，快取從記憶體中抓取的整個資料塊幾乎都是同行不同列的資料，而這些資料在接下來的內迴圈中完全無法被重複利用。 透過迴圈交換改進如下 a 1005000 初始化 for i 0 i 100 i i 1 for j 0 j 5000 j j 1 a ij 2 a ij 這樣，快取因為 a i0 失效而從記憶體中抓取的資料塊實際上覆蓋了 a i0 到 a i7 的全部資料（假定使用 32 位元組大小的快取塊，每個整型值佔四位元組）。這樣後邊連續七次內迴圈均可告命中。 2 迴圈合併（Loop fusion） 考慮下邊的程式碼 a 1000 初始化 for i 0 i 1000 i i 1 a i 2 a i for i 0 i 1000 i i 1 b i a i CONSTANT 如果編譯器可以證明兩個迴圈體可以合併到一個基本塊而不影響程式結果，則應該進行合併。這是因為，透過合併，原來第二個迴圈的語句在訪問記憶體時必然會命中。 合併後的程式碼 a 1000 初始化 for i 0 i 1000 i i 1 a i 2 a i b i a i CONSTANT 對 a i 的訪問必然命中快取 3 迴圈分塊（Blocking） 當迴圈遍歷的記憶體範圍很大（例如一個多維陣列）時，由於快取容積有限，可能會導致每次遍歷結束時留下的快取佈局完全無法被接下來的一次遍歷利用。這種情形下對迴圈進行分塊就十分有意義。 假設現在使用了一個非常小的全相聯快取，只有四個快取段，每個 16 位元組。二維整型陣列 b 和 c 的大小均為 10241024，並被儲存上記憶體的連續地址上。由於每個整數佔 4 個位元組，故在這個快取最多隻能容納 16 個整數。假定該快取使用 LRU 置換策略。首先考慮未經過最佳化的程式碼。這個程式碼段遍歷整個矩陣，每次遍歷過程中交替訪問由 i 和 j 分別指定的向量 b i0b i1023 和 c 0jc 1023j。 b 1024 初始化 c 1024 初始化 for i 0 i 1024 i i 1 for j 0 j 1024 j j 1 for k 0 k 1024 k k 1 b ik c kj 由於快取極小，這段程式碼效率十分低。考慮當 i0、j0 時，最內迴圈最後一次遍歷中，在訪問完 b ik（實際上是 b 01023）之後，但還沒有訪問 c kj（實際上是 c 10230）的情形，快取內容如下圖所示。 之後，訪問 c 10230，快取被重新整理為 這樣一個結果無疑對於下一次遍歷（i0、j1）毫無價值。因為在 k 自增到 4 之前，所有資料都無法被重複利用，結果只能被換出。但如果改成 b 1024 初始化 c 1024 初始化 int B 4 for jj 0 jj 1024 jj jj B for kk 0 kk 1024 kk kk B for i 0 i 1024 i i 1 for j jj j min jj B 1024 j j 1 for k kk k min kk B 1024 k k 1 b ik c kj 再次考慮當 jj0、kk0、i0、j0 時，最內迴圈最後一次遍歷中，在訪問完 b ik（實際上是 b 03）之後，但還沒有訪問 c kj（實際上是 c 30）的情形，快取內容如下圖所示。 之後，訪問 c 30，快取被重新整理為 這樣的結果對於下一次遍歷（jj0、kk0、i0、j1）就十分有價值，因為所需資料的大部分，包括 b 00b 03 和 c 11c 31，全都已在快取中。 此外，還有數組合並（Array merge）、迴圈分解（Loop fission）、分支取直（Branch Straightening）等多種技術。 預取 為了利用空間區域性性，同時也為了覆蓋傳輸延遲，可以隨機性地在資料被用到之前就將其取入快取。這一技術稱為預取（Prefetch）。本質上講，載入整個快取塊其實即是一種預取。 預取可以透過硬體或軟體控制。典型的硬體指令預取會在快取因失效從記憶體載入一個塊的同時，把該塊之後緊鄰的一個塊也傳輸過來。第二個塊不會直接進入快取，而是被排入指令流緩衝器（Instruction Stream Buffer）中。之後，當第二個記憶體訪問指令到來時，會並行嘗試從快取和流緩衝器中讀取。如果該資料恰好在流緩衝器中，則取消快取訪問指令，並將返回流緩衝器中的資料。同時，發出起一次新的預取。如果資料並不在流緩衝器中，則需要將緩衝器清空。 軟體控制則多由編譯器進行。指令集會提供預取指令供編譯器最佳化時使用。編譯器則負責分析程式碼，並把預取指令適當地插入其中。這類指令直接把目標預取資料載入快取。 在使用預取技術時，必須妥善考慮進行時機和實施強度。如果過早地進行預取，則有可能在預取資料被用到之前就已經因為衝突置換被清除。如果預取得太多或太頻繁，則預取資料有可能將那些更加確實地會被用到的資料取代出快取。 減輕失效代價 多級快取 相對於單級快取，多級快取的效能模型可以下式表示： AMAT T_L1hit MR_L1 times MP_L1 MP_L1 T_L2hit MR_L2 times MP_L2 由於全域性失效率等於兩個區域性失效率 MR_L1 和 MR_L2 之積，故使用多級快取降低了失效率。 讀失效優先策略 對於使用寫緩衝器的快取，當出現讀失效時會遇到一個問題：所要讀取的資料已經被修改，但是還沒有更新到記憶體。也就是說，新資料還在寫緩衝器中。 解決方案有二：一、等待，直到寫緩衝器清空。這種方法顯然效率不高。二、在讀快取的同時檢查寫緩衝器，確認最新資料是否在已在寫緩衝器中。如果有則直接從寫緩衝器返回。這種方法的本質是相比於回寫操作，賦予讀失效處理更高的優先順序。 關鍵詞優先 如前述，快取從記憶體讀取資料時需要把整個快取塊都填滿，再返回偏移指定單元給處理器。但其實可以做這樣的最佳化，即令快取從對應記憶體塊的相應偏移位置，也就是關鍵詞（Critical word），開始讀資料，然後一旦第一個資料單元被傳回，就立即將其交給處理器。 另有一個叫做早重啟（Early Restart）的類似技術。這種技術仍然從記憶體塊的起始位置按常序傳輸資料，但是一旦關鍵詞資料返回，就將其傳回處理器。可見，這種方法在減少處理器停滯上遜於關鍵詞優先法。 合併寫緩衝器 寫緩衝器通常可以令 1 到 4 個快取塊排隊等待回寫。但事實上大部分寫操作都只是針對快取塊的某一個單元進行的。同時，因為經常會出現對一塊連續記憶體按序進行寫操作（比如初始化一個數組），所以可以考慮將連續的寫操作合併為一個寫操作。 特殊的快取結構 指令 資料分離快取 由於流水線會在指令抓取和記憶體訪問兩個階段上訪問記憶體，如不增加快取埠將會造成結構性冒險（Structural hazard）。一種辦法是使用兩片一級快取，分別服務於指令抓取和記憶體訪問兩個階段。這樣，前一個一級快取稱為指令快取，後一個則為資料快取。從處理器的觀點來看，這相當於採用了哈佛架構的儲存系統。 受害者快取 所謂受害者快取（Victim Cache），是一個與直接匹配或低相聯快取並用的、容量很小的全相聯快取。當一個數據塊被逐出快取時，並不直接丟棄，而是暫先進入受害者快取。如果受害者快取已滿，就替換掉其中一項。當進行快取標籤匹配時，在與索引指向標籤匹配的同時，並行檢視受害者快取，如果在受害者快取發現匹配，就將其此資料塊與快取中的不匹配資料塊做交換，同時返回給處理器。 受害者快取的意圖是彌補因為低相聯度造成的頻繁替換所損失的時間區域性性。 硬體實現 晶片技術 快取晶片通常採用速度較快的 SRAM。一級快取一般與處理器同片封裝，二級快取則不一定。一種實現是把標籤儲存整合到片內，而把資料儲存放到片外。 控制器 可以使用有限狀態機實現簡易的快取控制器。Computer Organization And Design The HardwareSoftware Interface，第四版，Morgan Koffmann 出版，第 529 頁 歷史和未來 快取的發展史 在科研領域，C J Conti 等人於 1968 年在描述 36085 和 36091 系統性能差異時最早引入了快取記憶體（cache）一詞 Computer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 K52 頁。Alan Jay Smith 於 1982 年的一篇論文中引入了空間區域性性和時間區域性性的概念。Mark Hill 在 1987 年發明了 3C（Compulsory Capacity Conflict）衝突分類。Computer Architecture A Quantitative Approach，第四版，Morgan Koffmann 出版，第 K53 頁 最早介紹非阻塞快取的論文之一來自 David Kroft（1981 年）。1990 年 Norman Paul Jouppi 在一篇論文中介紹了受害者快取並研究了使用流緩衝器進行預取的效能。 在工業領域，最早的有記載的快取出現在 IBM 的 36085 系統上 IBM System360 Model 85 Functional Characteristics 第二版 ，IBM。 Intel 的 x86 架構 CPU 從 386 開始引入使用 SRAM 技術的主機板快取，大小從 16KB 到 64KB 不等。486 引入兩級快取。其中 8KBL1 快取和 CPU 同片，而 L2 快取仍然位於主機板上，大小可達 268KB。將二級快取置於主機板上在此後十餘年間一直設計主流。但是由於 SDRAM 技術的引入，以及 CPU 主頻和主機板匯流排頻率的差異不斷拉大，主機板快取在速度上的對記憶體優勢不斷縮水。因此，從 Pentium Pro 起，二級快取開始和處理器一起封裝，頻率亦與 CPU 相同（稱為全速二級快取）或為 CPU 主頻的一半（稱為半速二級快取）。 AMD 則從 K6III 開始引入三級快取。基於 Socket 7 介面的 K6III 擁有 64KB 和 256KB 的同片封裝兩級快取，以及可達 2MB 的三級主機板快取。 還有 CPU 將三級快取全部整合到 CPU 晶片上。多核 CPU 通常為每個核配有獨享的一級和二級快取，以及各核之間共享的三級快取。 當今的研究方向 早期的快取設計主要考慮的是儲存器成本和平均訪問速度。而許多最新的快取設計同時關注了能耗、容錯等其它指標。 在進行快取效能研究時，通常使用軟體模擬技術。有許多這樣的開源軟體，包括 CACTI（Norm Paul Jouppi 等人），以及 SimpleScalar（Todd Austin， 威斯康星大學麥迪遜分校）等。 參見 分支預測器 快取 快取檔案置換機制 快取一致性 快取控制指令 （威斯康星大學開發的快取模擬器） 訪問區域性性 記憶化 記憶體階層 註釋 Category 電腦記憶體 Category 微處理器 Category 快取