多線程時如何使用CPU緩存?
共 9032字,需瀏覽 19分鐘
·
2024-09-11 07:33
作者:shengjk1
鏈接:https://juejin.cn/post/7390480675172335666
一、前言
計算機的基礎知識聊的比較少,但想要更好的理解多線程以及為后續(xù)多線程的介紹做鋪墊,所以有必要單獨開一篇來聊一下 CPU cache。
二、CPU
前面有一篇文章關于 CPU是如何進行計算 感興趣的同學,可以先移步了解一下,不了解也沒有關系,不影響這這篇文章的理解。
2.1 CPU 發(fā)展史以及為什么需要 cpu cache
時間回到1978年,第一顆在個人PC上使用的微處理器——8088,它的主頻才4.77MHz,導致當時CPU的存取時間(800ns左右)遠大于內存的存取時間(200ns左右),所以那時候根本不需要Cache。
到80386開始,CPU的頻率一下提高到40MHz,但是內存的上升速度卻沒有象 CPU一樣快,導致沒有相匹配的內存可以使用,使得CPU要耗費幾個,十幾個時鐘周期來等待內存的讀寫,這顯然不能讓人接受,于是有兩種解決方案同時被提出來:一種是在CPU內加入等待周期,降低CPU的處理能力;而另一種就是尋找一種速度快,面積小,延遲短的存儲體來做CPU與內存的中轉站,也就是我們現(xiàn)在所說的Cache(緩存)。第一種方法顯然是自欺欺人,犧牲CPU的性能來換取整體的平衡,所以第二種方法立刻被采用。
不過在386時期,由于成本的問題,并沒有內部L1 Cache,只有外部的Cache。而486時代,CPU頻率再次增加,外部Cache的速度也要相應提高,使得成本太高,于是內嵌了8K的L1 Cache,同時也可以使用外部的L2 Cache。
直到Pentium時代,由于Pentium采用了雙路執(zhí)行的超標量結構,有2條并行整數(shù)流水線,需要對數(shù)據(jù)和指令進行雙重的訪問,為了使得這些訪問互不干涉,于是出現(xiàn)了8K數(shù)據(jù)Cache和8K指令Cache,并且可以同時讀寫,不過L2還是外部的,接著出現(xiàn)的Pentium Pro為了提高性能,把L2內嵌了,到此為止,就確定了現(xiàn)代緩存的基本模式了,并一直沿用至今。
這里還有一些關于 cacha 的訪問數(shù)據(jù),從數(shù)據(jù)中可以看出 L1 cache 相比于訪問主存,性能提高 200 倍
2.2 CPU Cache 是什么
通常cpu內有3級緩存,即L1、L2、L3緩存。其中L1緩存分為數(shù)據(jù)緩存和指令緩存,cpu先從L1緩存中獲取指令和數(shù)據(jù),如果L1緩存中不存在,那就從L2緩存中獲取。每個cpu核心都擁有屬于自己的L1緩存和L2緩存。如果數(shù)據(jù)不在L2緩存中,那就從L3緩存中獲取。而L3緩存就是所有cpu核心共用的。如果數(shù)據(jù)也不在L3緩存中,那就從內存中獲取了。當然,如果內存中也沒有那就只能從硬盤中獲取了。
2.2.1 CPU Cache 原理
CPU Cache利用時間局部性和空間局部性原理來優(yōu)化數(shù)據(jù)訪問性能。這兩個原理在Cache工作中扮演關鍵角色,以下是對CPU Cache工作原理與時間局部性、空間局部性的詳細解釋:
時間局部性:
概念:時間局部性意味著一旦數(shù)據(jù)被訪問,它在不久的將來會再次被訪問。
Cache應用:當處理器訪問一個數(shù)據(jù)后,該數(shù)據(jù)會被保留在Cache中,因為存在時間局部性,表示該數(shù)據(jù)在短期內可能再次被訪問。
優(yōu)勢:通過利用時間局部性,Cache能夠減少對主存的訪問次數(shù),提高數(shù)據(jù)訪問速度,因為處理器在未來訪問相同數(shù)據(jù)時可以直接從Cache中獲取。
空間局部性:
概念:空間局部性表明一旦訪問了一個數(shù)據(jù),其臨近的數(shù)據(jù)也有可能被訪問。
Cache應用:由于空間局部性,Cache可能會預先加載鄰近數(shù)據(jù)塊,以提高整體的Cache命中率。
優(yōu)勢:通過存儲相鄰數(shù)據(jù)塊,Cache利用空間局部性可以更有效地提供處理器可能需要的數(shù)據(jù),減少Cache未命中的情況。
工作原理:
緩存命中:當處理器請求數(shù)據(jù)時,首先在Cache中查找。如果數(shù)據(jù)存在于Cache中,發(fā)生緩存命中,處理器直接從Cache讀取數(shù)據(jù)。
緩存未命中:如果請求的數(shù)據(jù)未在Cache中找到,發(fā)生緩存未命中,需要從主存加載數(shù)據(jù)到Cache中。
時間局部性應用:當數(shù)據(jù)被訪問并存儲在Cache中,根據(jù)時間局部性,該數(shù)據(jù)在不久的將來可能再次被訪問。
空間局部性應用:根據(jù)空間局部性,Cache可能會預取與即將被訪問的數(shù)據(jù)相關的鄰近數(shù)據(jù)塊,以提前加載可能被訪問的數(shù)據(jù)。
優(yōu)化策略:
數(shù)據(jù)塊大小:選擇適當?shù)臄?shù)據(jù)塊大小以最大化空間局部性的利用。
替換策略:設計針對時間局部性的替換策略,保留最近使用的數(shù)據(jù)。
預取策略:根據(jù)空間局部性預先加載可能被訪問的數(shù)據(jù),以提高Cache命中率。
綜合利用時間局部性和空間局部性原理,CPU Cache能夠極大地優(yōu)化數(shù)據(jù)訪問性能,減少主存訪問延遲,提高處理器的運行效率,是計算機系統(tǒng)中至關重要的組件之一。
2.2.2 CPU Cache 實現(xiàn)
2.2.2.1 CPU Cache 是通過 SRAM 實現(xiàn)的
在現(xiàn)代計算機中,我們最熟悉的半導體存儲體有三種:
用于存儲BIOS信息的EEPROM(Electrically Erasable Programmable Read Only Memory,電可擦寫可編程只讀存儲器)
用于存儲臨時工作數(shù)據(jù)的DRAM(Dynamic Random Access Memory,動態(tài)隨機訪問存儲器)
SRAM(Static Random Access Memory,靜態(tài)隨機訪問存儲器)。
EEPROM由于成本過高,速度太慢,肯定不能做為Cache材料的選擇,而DRAM和SRAM兩種,為什么是SRAM用來做Cache,而不是DRAM呢?當然最大的原因是速度。
我們知道DRAM目前最常見的應用就是內存了,它是利用每個單元的寄生電容來保存信號的,正因如此,它的信號強度就很弱,容易丟失,所以DRAM需要時時刷新來確定其信號(根據(jù)DRAM制造商的資料,DRAM至少64ms要刷新一次,并且由于每次讀取操作都會破壞DRAM中的電荷,所以每次讀取操作之后也要刷新一次,這就表示,DRAM至少有1%的時間是用來刷新的)。這樣,DRAM的存儲周期就增大了,當然潛伏期也變大了。我們從Cache的起源可以看出,正是因為內存的讀取時間過高而引入Cache的,所以DRAM不符合做Cache的標準。
而SRAM不是通過電容充放電來存儲數(shù)據(jù),而是利用設置晶體管的狀態(tài)來決定邏輯狀態(tài),讓其保存數(shù)據(jù),并且這種邏輯狀態(tài)和CPU本身的邏輯狀態(tài)相象,換句話說,SRAM可以以接近CPU頻率的速度來運行(在今年秋季的IDF上,Intel公布的65nm工藝的SRAM,可以???行在3.4GHz的頻率上),再加上讀取操作對SRAM是不具破壞性的,不存在刷新問題,大大的縮短了潛伏期。所以這種低延遲,高速度的SRAM才是Cache材料的首選。
2.2.2.2 為什么不用寄存器實現(xiàn)
雖然寄存器和緩存都用于存儲數(shù)據(jù)以提高處理器的性能,但它們有不同的設計目的和工作方式,導致無法直接將寄存器用作緩存。這里解釋為什么CPU不使用寄存器來替代緩存:
速度和容量:
速度:寄存器通常是CPU內部最快的存儲器,訪問速度非常快,但寄存器的容量非常有限。緩存雖然比主存慢,但容量更大,能夠存儲更多數(shù)據(jù)并且與處理器核心之間的傳輸速度也更接近。
容量:寄存器的數(shù)量非常有限,一般只有幾十個甚至更少,而緩存可以容納更多數(shù)據(jù),提供更大的數(shù)據(jù)集合供處理器訪問,利用了空間局部性和時間局部性的原理。
成本和復雜性:
成本:寄存器的成本非常高昂,因為它們需要在CPU內部直接連接到執(zhí)行單元。在CPU中集成大量寄存器會顯著增加芯片成本,而緩存雖然也會增加成本,但比寄存器便宜。
復雜性:設計一個大規(guī)模的寄存器集合管理和調度是非常復雜的,而緩存的管理和替換策略相對容易實現(xiàn)。
共享和處理器設計:
共享性:寄存器是每個處理器核心私有的,而緩存是共享的,多個核心可以共享同一級別的緩存。這種共享能夠更好地支持多核處理器的設計。
處理器設計:處理器的設計通常會包含多級緩存結構,每個級別的緩存都有特定的目的和功能,以提供更好的性能均衡。寄存器是用于存儲指令、數(shù)據(jù)和中間結果,而緩存是用于加速訪問主存的部分數(shù)據(jù)。
綜上所述,雖然寄存器和緩存都用于存儲數(shù)據(jù)以提高處理器性能,但由于速度、容量、成本、復雜性和處理器設計等方面的考慮,CPU不直接使用寄存器作為緩存的替代品。不同的存儲器層次結構在處理器設計中起著各自不同的重要作用。
對CPU cache 整體了解之后,我們就可以進一步了解高速緩存的內部細節(jié)了
2.2.2 CPU Cache 內部細節(jié)
CPU Cache 在讀取內存數(shù)據(jù)時,每次不會只讀一個字或一個字節(jié),而是一塊塊地讀取,這每一小塊數(shù)據(jù)也叫 CPU 緩存行(CPU Cache Line)。也就是說CPU 緩存和內存之間交換數(shù)據(jù)的最小單位通常是緩存行(Cache Line)。緩存行是緩存中數(shù)據(jù)的最小存儲單位,在CPU和主內存之間傳輸數(shù)據(jù)時,以緩存行為單位進行數(shù)據(jù)傳輸和管理。
這也是對局部性原理的運用,當一個指令或數(shù)據(jù)被拜訪過之后,與它相鄰地址的數(shù)據(jù)有很大概率也會被拜訪,將更多或許被拜訪的數(shù)據(jù)存入緩存,可以進步緩存命中率。
在現(xiàn)代計算機系統(tǒng)中,通常使用的緩存(Cache)類型主要分為三種:直接映射緩存(Direct-Mapped Cache)、多路組相連緩存(Set-Associative Cache)和全相連緩存(Fully Associative Cache)。不同的緩存類型有其特點和適用場景。至于具體哪種類型的緩存用于計算機的L1、L2或L3緩存,這取決于具體的處理器架構和制造商的實現(xiàn)。
以下是這三種緩存類型的簡要介紹:
直接映射緩存(Direct-Mapped Cache):在這種緩存中,每個緩存行只能存儲一個特定的主存地址或地址范圍的數(shù)據(jù)。每個緩存行都有一個唯一的標簽與之關聯(lián),用于確定數(shù)據(jù)是否存在于緩存中。這種映射方式比較簡單和高效,適用于高速緩存系統(tǒng)。它有一個主要的缺點是緩存行的使用不夠靈活,如果多個不同的地址都需要同一個緩存行大小的數(shù)據(jù),可能會造成沖突和性能下降。
多路組相連緩存(Set-Associative Cache):在這種緩存中,一個緩存行可以存儲多個可能的地址范圍的數(shù)據(jù)。這種類型的緩存允許多個主存地址共享相同的緩存行,并可以通過一個或多個標簽來標識每個緩存行中的不同數(shù)據(jù)。這種設計提供了更大的靈活性,但查找操作可能需要更多的時間,因為處理器需要在多組選項中做出選擇。這在緩存使用較多時會減少命中時間偏差的變化,有利于提高平均命中率。在計算機中經(jīng)常使用的是所謂的偽相聯(lián)策略,結合直接映射和全相連策略的特點。
全相連緩存(Fully Associative Cache):在這種類型的緩存中,沒有任何限制條件用于決定哪些地址范圍的數(shù)據(jù)可以存儲在同一個緩存行中。每個緩存行都可以存儲任何地址的數(shù)據(jù),這使得查找操作相對復雜且耗時較長。然而,這種靈活性使得全相連緩存能夠在某些情況下實現(xiàn)最佳的性能優(yōu)化。這種類型在現(xiàn)代計算機系統(tǒng)中使用較少。在實際的處理器設計中,不同層次的緩存可能采用不同的策略以滿足性能和能效的平衡需求。比如現(xiàn)代計算機可能在多級緩存中采用不同的映射策略組合,以適應不同的應用場景和需求。因此,具體的實現(xiàn)取決于處理器的架構和制造商的選擇。建議您查閱具體的處理器文檔或參考相關技術文檔來獲取最準確的信息。
接下來一一解釋一下:
2.2.2.1 直接映射緩存
直接映射緩存會將一個內存地址固定映射到某一行的cache line。
其思想是將一個內存地址劃分為三塊,分別是Tag, Index,Offset(這里的內存地址指的是虛擬內存)。將cacheline理解為一個數(shù)組,那么通過Index則是數(shù)組的下標,通過Index就可以獲取對應的cache-line。再獲取cache-line的數(shù)據(jù)后,獲取其中的Tag值,將其與地址中的Tag值進行對比,如果相同,則代表該內存地址位于該cache line中,即cache命中了。最后根據(jù)Offset的值去data數(shù)組中獲取對應的數(shù)據(jù)。整個流程大概如下圖所示:
下面是一個例子,假設cache中有8個cache line,
對于直接映射緩存而言,其內存和緩存的映射關系如下所示:
從圖中我們可以看出,0x00,0x40,0x80這三個地址,其地址中的index成分的值是相同的,因此將會被加載進同一個cache line。
試想一下如果我們依次訪問了0x00,0x40,0x00會發(fā)生什么?
當我們訪問0x00時,cache miss,于是從內存中加載到第0行cache line中。當訪問0x40時,第0行cache line中的tag與地址中的tag成分不一致,因此又需要再次從內存中加載數(shù)據(jù)到第0行cache line中。最后再次訪問0x00時,由于cache line中存放的是0x40地址的數(shù)據(jù),因此cache再次miss。可以看出在這個過程中,cache并沒有起什么作用,訪問了相同的內存地址時,cache line并沒有對應的內容,而都是從內存中進行加載。
這種現(xiàn)象叫做cache顛簸(cache thrashing)。針對這個問題,引入多路組相連緩存。下面一節(jié)將講解多路組相連緩存的工作原理。
2.2.2.2 多路組相連緩存
多路組相連緩存的原理相比于直接映射緩存復雜一些,這里將以兩路組相連這種場景來進行講解。
所謂多路就是指原來根據(jù)虛擬的地址中的index可以唯一確定一個cache line,而現(xiàn)在根據(jù)index可以找到多行cache line。而兩路的意思就是指通過index可以找到2個cache line。在找到這個兩個cache line后,遍歷這兩個cache line,比較其中的tag值,如果相等則代表命中了。
下面還是以8個cache line的兩路緩存為例,假設現(xiàn)在有一個虛擬地址是0000001100101100,其tag值為0x19,其index為1,offset為4。那么根據(jù)index為1可以找到兩個cache line,由于第一個cache line的tag為0x10,因此沒有命中,而第二個cache line的tag為0x19,值相等,于是cache命中。
對于多路組相連緩存而言,其內存和緩存的映射關系如下所示:
由于多路組相連的緩存需要進行多次tag的比較,對于比直接映射緩存,其硬件成本更高,因為為了提高效率,可能會需要進行并行比較,這就需要更復雜的硬件設計。
另外,如何cache沒有命中,那么該如何處理呢?
以兩路為例,通過index可以找到兩個cache line,如果此時這兩個cache line都是處于空閑狀態(tài),那么cache miss時可以選擇其中一個cache line加載數(shù)據(jù)。如果兩個cache line有一個處于空閑狀態(tài),可以選擇空閑狀態(tài)的cache line 加載數(shù)據(jù)。如果兩個cache line都是有效的,那么則需要一定的淘汰算法,例如PLRU/NRU/fifo/round-robin等等。
這個時候如果我們依次訪問了0x00,0x40,0x00會發(fā)生什么?
當我們訪問0x00時,cache miss,于是從內存中加載到第0路的第0行cache line中。當訪問0x40時,第0路第0行cache line中的tag與地址中的tag成分不一致,于是從內存中加載數(shù)據(jù)到第1路第0行cache line中。最后再次訪問0x00時,此時會訪問到第0路第0行的cache line中,因此cache就生效了。由此可以看出,由于多路組相連的緩存可以改善cache顛簸的問題。
2.2.2.3 全相連緩存
從多路組相連,我們了解到其可以降低cache顛簸的問題,并且路數(shù)量越多,降低cache顛簸的效果就越好。那么是不是可以這樣設想,如果路數(shù)無限大,大到所有的cache line都在一個組內,是不是效果就最好?基于這樣的思想,全相連緩存相應而生。
下面還是以8個cache line的全相連緩存為例,假設現(xiàn)在有一個虛擬地址是0000001100101100,其tag值為0x19,offset為4。依次遍歷,直到遍歷到第4行cache line時,tag匹配上。
全連接緩存中所有的cache line都位于一個組(set)內,因此地址中將不會劃出一部分作為index。在判斷cache line是否命中時,需要遍歷所有的cache line,將其與虛擬地址中的tag成分進行對比,如果相等,則意味著匹配上了。因此對于全連接緩存而言,任意地址的數(shù)據(jù)可以緩存在任意的cache line中,這可以避免緩存的顛簸,但是與此同時,硬件上的成本也是最高。
2.2.3 Cpu Cache Line 空閑
無論是三種緩存類型的哪種,Cache Line 要想被使用則必須處于空閑狀態(tài)。
當說一個cache line(緩存行)處于空閑狀態(tài)時,通常是指該緩存行當前沒有有效的數(shù)據(jù)或有效的標記。在多級緩存中,每個緩存行通常包含了用于存儲數(shù)據(jù)的存儲單元、標記(tag)用于標識緩存行中數(shù)據(jù)的來源(比如主存地址),以及其他控制信息。
以下是關于緩存行空閑狀態(tài)和標識碼的解釋:
空閑狀態(tài):
當一個cache line 被說成“空閑時”,意味著該緩存行當前不包含有效的數(shù)據(jù)。這可能是因為該緩存行還未被加載、已經(jīng)被替換出去、還沒有被寫入數(shù)據(jù),或者數(shù)據(jù)已被清除等情況。
標識碼(Tag):
在直接映射緩存或其他高速緩存設計中,每個緩存行都會有一個標識碼(tag),用于標識該緩存行中數(shù)據(jù)在主存中的地址范圍,并區(qū)分不同的緩存行。
當某個緩存行處于空閑狀態(tài)時,其相應的標識碼可能不存在或被標記為無效,因為沒有合法的數(shù)據(jù)與之對應。
在高速緩存中,空閑狀態(tài)的緩存行是指該緩存行當前沒有有效的數(shù)據(jù),可能會在后續(xù)的訪問中被加載、寫入或更新。標識碼通常用于查找特定數(shù)據(jù)的方法,當緩存行處于空閑狀態(tài)時,標識碼可能會被標記為無效或未被使用,以便在未來的訪問中正確地識別和處理遷移數(shù)據(jù)。
2.2.3 Cpu Cache Line 大小
CPU的緩存線(cache line)大小是在設計階段確定的固定值,通常由CPU架構的設計者根據(jù)性能需求和成本考慮來確定。不同的處理器架構可能會采用不同大小的緩存線,常見的緩存線大小通常是64字節(jié)、128字節(jié)或更大。以下是一些常見的緩存線大小選項:
64字節(jié):
許多現(xiàn)代處理器(如英特爾和AMD的一些架構)使用64字節(jié)的緩存線大小。這意味著每次從主存中加載數(shù)據(jù)時,處理器會將整個64字節(jié)的緩存行加載到緩存中,即使只需要其中的一部分數(shù)據(jù)。
128字節(jié):
有些處理器采用128字節(jié)的緩存線大小,這種設計能夠更大程度上利用空間局部性的特點,提供更多數(shù)據(jù)以供處理器在未來訪問。
其他大小:
除了64字節(jié)和128字節(jié),還有一些處理器架構可能選擇其他大小的緩存線,具體取決于設計者的需求和考慮。
緩存線的大小主要受到以下因素的影響:
空間局部性:較大的緩存行可以更好地利用空間局部性,一次加載更多數(shù)據(jù),減少對主存的頻繁訪問。
性能需求:較大的緩存行可能提供更好的性能,但也會增加緩存的開銷和復雜性。
成本:較大的緩存行會增加對緩存存儲器和總線帶寬的需求,可能導致更高的硬件成本。
因此,選擇緩存線的大小是一個權衡取舍的過程,需要考慮多個因素以找到最適合特定處理器架構和應用場景的大小。
2.2.3.1 如何查看服務器中 Cpu Cache Line 大小
要確定服務器的緩存行大小,可以采取以下幾種方法:
查閱處理器規(guī)格:查看服務器所用處理器的規(guī)格說明書或相關文檔。處理器制造商通常會在其技術規(guī)格文檔中提供有關緩存的詳細信息,包括緩存層次結構、緩存行大小等。通過查找處理器型號和規(guī)格信息,您應該能夠找到緩存行的大小。
使用 CPU-Z 或類似工具:CPU-Z 是一種常用的免費工具,可以用來查看計算機的硬件信息,包括處理器型號、緩存大小和緩存行大小等。通過運行 CPU-Z 或類似的硬件信息工具,您可以查看服務器使用的處理器類型以及相關的緩存信息。
操作系統(tǒng)命令:在某些操作系統(tǒng)中,您可以使用特定的命令來查看系統(tǒng)的硬件信息,包括處理器的緩存相關信息。例如,在 Linux 環(huán)境下可以使用命令如下來查看緩存信息:
秋招已經(jīng)開始啦,大家如果不做好充足準備的話,秋招很難找到好工作。
送大家一份就業(yè)大禮包,大家可以突擊一下春招,找個好工作!
