1. 程式人生 > >8.6 高速緩存的設計要點

8.6 高速緩存的設計要點

成本 今天 這樣的 階段 http 共享 帶來 剛才 命中

計算機組成

8 存儲層次結構

8.6 高速緩存的設計要點

技術分享圖片

高速緩存是一個非常精細的部件,想讓它高效地工作,就得在設計時,進行仔細地權衡。想要設計出一個優秀的高速緩存部件,我們就得從幾個基本概念開始入手。

技術分享圖片

我們再來看一看Cache的訪問過程。如果CPU向Cache發出了讀請求,Cache就會檢查對應的數據是否在自己內部。

如果在,我們就稱為Cache命中。有項性能指標就叫做命中率,這說的是CPU所有訪存請求中發生了Cache命中的請求所占的比例。如果命中就從Cache內部向CPU返回數據。從Cache中將命中的數據返回的時間,就稱為命中時間,這也是一個重要的性能參數。現在的CPU當中,一級開始的命中時間大約是1到3個周期,二級Cache的命中時間大約是5到20個周期。

如果數據不在Cache裏,我們就稱為Cache失效,對應的就是失效率。失效率和命中率加在一起肯定是百分之一百。在失效之後,Cache會向主存發起讀請求,那麽等待從主存中返回讀數據的這個時間,我們就稱為失效代價。現在通常需要在100到300個時鐘周期之後,才能得到讀的數據。

技術分享圖片

那要評價訪存的性能,我們經常會用到平均訪存時間這個指標,這個指標就是用剛才介紹的那幾個參數推算出來的。平均訪存時間就等於命中時間,加上失效代價乘以失效率。想要提高訪存的性能,我們就得降低平均訪存時間,要做的就是分別降低這三個參數,這就是提高訪存性能的主要途徑。

其中想要降低命中時間,就要盡量將Cache的容量做得小一些,Cache的結構也不要做得太復雜。但是小容量的結構簡單的Cache,又很容易發生失效,這樣就會增加平均訪存時間。其中如果要減少失效代價, 要麽是提升主存的性能,要麽是在當前的高速緩存和主存之間再增加一級高速緩存。那在新增的那級高速緩存當中,也需要面臨這些問題。所以,這三個途徑並不是獨立的,它們是交織在一起相互影響。那我們先重點來看一看命中率這個因素。

如果有一個Cache的命中率是97%,我們假設命中時間是3個周期,失效代價是300個周期。那麽平均訪存時間就是12個周期。那如果我們有一個方法可以在不影響命中時間和失效代價的情況下,將命中率提高到99%,這時候的平均訪存時間就降低到了6個周期。所以,雖然命中率只提高了2%,看起來並不起眼,但是訪存性能卻提高了一倍,這是非常大幅度的提升。所以,對於現在的Cache來說,能夠提高一點點命中率,都可以帶來很好的性能提升。

那哪些因素會影響命中率呢?

技術分享圖片

或者我們反過來看,Cache失效會由哪些原因造成?

有一種叫做義務失效。從來沒有訪問過的數據塊,肯定就不在Cache裏。所以,第一次訪問這個數據塊所發生的失效,就稱為義務失效。義務失效是很難避免的。

第二種失效原因稱為容量失效。如果這個程序現在所需的數據塊已經超過了這個Cache容量的總和,這樣不管我們怎麽去精巧地設計這個Cache,總會發生失效。當然容量失效可以通過擴大Cache來緩解,但是增加了Cache容量之後,一方面會增加成本,另一方面可能也會影響到命中時間。所以,也需要綜合地考慮。

第三種失效稱為沖突失效。也就是在Cache並沒有滿的情況下,因為我們將多個存儲器的位置映射到了同一個Cache行,導致位置上的沖突而帶來的失效。我們重點就來看一看如何解決這個問題。這個問題就稱為Cache的映射策略。

技術分享圖片

那這是一塊存儲器的區域,我們還是按照每16個字節一個數據塊(十六進制地址最低位為0)的形式把這塊存儲器的區域畫出來。所以,地址這一列標記的是各個數據塊的起始地址,每個地址之間正好相差16。那如果我們有一個8表項的Cache,那麽就采用我們之前介紹過的那種存放方法。地址為0的數據塊是要放在表項0當中;而地址為080的數據塊,也得放在表項0當中;同樣地址為100的數據塊,也要存放在這個表項中。這其實就是把內存分成每8個數據塊為一組,任何一組當中的第0個數據塊,都會被放在表項0,第一個數據塊都會被放在表項1,這樣的Cache的映射策略就叫做直接映射。它的優點在於硬件結構非常簡單,我們只要根據地址就可以知道對應的數據塊應該放在哪個表項,但是,它的問題也很明顯。如果我們在程序當中正好要交替地不斷訪問兩個數據,不妨稱為數據A和數據B。如果數據A在地址為0的這個數據塊當中,而數據B在地址080的這個數據塊當中,那在訪問到數據A時,會把地址0的這個數據塊調到Cache當中來,然後把對應的數據A交給CPU;然後CPU又需要訪問數據B,其後開始發現,數據B不在Cache當中,所以又要把080對應的這個數據塊調進來,替換掉原有的表項0當中的數據,然後再將數據B交給CPU。然後,接下來CPU又訪問到數據A,那Cache又要把地址為000H的這個數據塊調進來,再次覆蓋表項0。那如果CPU不斷地在交替訪問數據A和數據B,這段時間的Cache訪問每一次都是不命中的,這樣的訪存性能還不如沒有Cache,而這時Cache當中其他的行都還是空著的,根本沒有發揮作用。所以,這就是這個映射策略上的問題。

為了解決這個問題,我們可以做一些改進,在不增加Cache總的容量情況下,我們可以將這8個Cache行分為兩組,這就是二路組相聯的Cache。這樣剛才那種交替地訪問數據A和數據B的情況,就不會有問題了,因為CPU訪問數據A時,就會把地址0對應的數據塊放在這裏,而接下來在訪問數據B時,就會把地址080對應的數據塊放在這裏。然後,再反復地訪問數據A和數據B,都會在Cache中命中,這樣訪存的性能就會非常好。當然如果CPU在交替地訪問這三個數據(000H,080H,100H)塊當中的數據,那麽二路組相聯的Cache又會出現連續不命中的情況。所以,我們還可以對它進一步切分。這就是一個四路組相聯的Cache。

我們是不是可以無限制地切分下去呢?這倒是可以的。如果這個Cache總共只有8行,而我們把它分成八路組相聯,那也就是說,內存當中任一個數據塊都可以放到這個Cache當中的任何一個行中,而不用通過地址的特征來約定固定放在哪一個行,這樣結構的Cache就叫做全相聯的Cache。這樣的設計靈活性顯然是最高的,但是它的控制邏輯也會變得非常的復雜。我們假設CPU發了一個地址,Cache要判斷這個地址是否在自己內部,它就需要把可能包含這個地址的Cache行當中的標簽取出來進行比較。對於直接映射的Cache,只需要取一個標簽來比較就行;二路組相聯的時候,就需要取兩個標簽同時進行比較;四路組相聯的時候就需要取出四個標簽來比較;而在全相聯的情況下,那就需要把所有行當中的標簽都取出來比較。

這樣的比較需要選用大量的硬件電路,既增加了延遲,又增加了功耗。如果劃分的路數太多,雖然有可能降低了失效率,但是卻增加了命中時間,這樣就得不償失了。而且話又說回來,增加了路數,還不一定能夠降低失效率。

技術分享圖片

因為在多路組相聯的Cache當中,同一個數據塊可以放在不同的地方。如果這些地方都已經被占用了,就需要去選擇一行替換出去,這個替換算法設計得好不好,就對性能有很大的影響。如果這個Cache選擇替換出去的行,恰恰總是過一會就要使用到的那個數據塊,那這樣性能的表現就會很差。

現在常見的Cache替換算法有這幾種。最簡單的是隨機替換,這個性能顯然不會很好。然後還有輪轉替換,也就是按照事先設定好的順序依次地進行替換,如果是四路組相聯,上一次替換了第0路,這一次就替換第1路,下一次就替換第2路,再下一次就替換第3路。這個從硬件設計設計上來說比較簡單,但是性能也一般。性能比較好的替換算法,是最近最少使用的替換算法,簡稱為LRU,它需要額外的硬件來記錄訪問的歷史信息,在替換的時候,選擇距離現在最長時間沒有被訪問的那個Cache行進行替換。在使用中,這種方法的性能表現比較好,但是其硬件的設計也相當的復雜。所以,映射策略和替換算法都需要在性能和實現代價之間進行權衡。

那我們再來看看一些Cache設計的實例。

技術分享圖片

在X86系列CPU當中,486是最早在CPU芯片內部集成了Cache的,但它使用的是一個指令和數據共用的Cache。這個Cache有一個很明顯的缺點,那就是指令和數據的局部性會互相影響。因為指令和數據一般是存放在內存中的不同區域,所以它們各自具有局部性。利用在執行一個要操作大量數據的程序,這些數據就會很快地占滿Cache,把其中的指令都擠出去了,在這個時候,執行一條指令,取指的階段很可能是Cache不命中,需要等待訪問存儲器,那就需要花很長的時間。而在執行階段,去取操作數時,卻往往會命中Cache,雖然這段時間比較短,但是整個指令執行的時間還是很長。

技術分享圖片

所以到了後來的奔騰,就把指令和數據分成了兩個獨立的Cache,這樣它們各自的局部性就不會相互影響了。

現在大多數CPU的一級Cache都會采用這樣的形式。

技術分享圖片

這個是現在比較先進的Core i7。它內部采用了多級Cache的結構,其中一級Cache是指令和數據分離的各32K個Byte,采用了8路組相聯的形式,命中時間是4個周期。所以,在CPU的流水線當中,訪問Cache也需要占多個流水級。

那麽在這個4核的i7當中,每個處理器核還有自己獨享的二級Cache。二級Cache就不再分成指令和數據兩個部分了,因為它的容量比較大,指令和數據之間的相互影響就不那麽明顯。但是二級Cache的命中時間也比較長,需要11個周期,Core i7 CPU的流水線總共也就16級左右,肯定是沒有辦法和二級Cache直接協同工作的。這也是為什麽一級Cache不能做得很大的一個重要原因(做大了需要花費多個流水級)。

在二級Cache之下,還有一個三級Cache。這是由四個核共享的,總共8兆個字節。三級Cache采用了16路組相聯的結構,而且容量也很大,達到了8兆個字節,這又導致它的命中時間很長,需要30到40個周期,但它這樣的結構命中率會很高,這樣就很少需要去訪問主存了。

我們可以看到這三級的Cache,它的命中時間從4個周期、11個周期到40個周期,我們再考慮到主存的100到300個周期,就可以看出這個多級Cache + 主存的結構就拉開了明顯的層次,在各自的設計時就可以有不同的側重,相互配合來提升整個系統的性能。

技術分享圖片

高速緩存的研究已經持續了很長時間,直到今天仍然是一個研究的熱點。只不過之前的研究對象是CPU和主存之間的這一級高速緩存,而現在的研究對象則是由多級高速緩存組成的這麽一個多層次的結構。不管怎麽樣,高速緩存技術的研究給我們帶來了在可控成本之下,盡可能高的系統性能。

8.6 高速緩存的設計要點