0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

cache對寫好代碼真的有那么重要嗎

B4Pb_gh_6fde77c ? 來源:CSDN ? 作者:宋寶華 ? 2021-07-26 15:18 ? 次閱讀

CACHE基礎(chǔ)

對cache的掌握,對于Linux工程師(其他的非Linux工程師也一樣)寫出高效能代碼,以及優(yōu)化Linux系統(tǒng)的性能是至關(guān)重要的。簡單來說,cache快,內(nèi)存慢,硬盤更慢。在一個典型的現(xiàn)代CPU中比較接近改進(jìn)的哈佛結(jié)構(gòu),cache的排布大概是這樣的:

b673da5a-e032-11eb-9e57-12bb97331649.png

L1速度》 L2速度》 L3速度》 RAM

L1容量《 L2容量《 L3容量《 RAM

現(xiàn)代CPU,通常L1 cache的指令和數(shù)據(jù)是分離的。這樣可以實(shí)現(xiàn)2條高速公路并行訪問,CPU可以同時load指令和數(shù)據(jù)。當(dāng)然,cache也不一定是一個core獨(dú)享,現(xiàn)代很多CPU的典型分布是這樣的,比如多個core共享一個L3。比如這臺的Linux里面運(yùn)行l(wèi)stopo命令:

b67c8c72-e032-11eb-9e57-12bb97331649.png

人們也常常稱呼L2cache為MLC(MiddleLevel Cache),L3cache為LLC(Last LevelCache)。這些Cache究竟有多塊呢?我們來看看Intel的數(shù)據(jù),具體配置:Intel i7-4770 (Haswell), 3.4 GHz (Turbo Boostoff), 22 nm. RAM: 32 GB (PC3-12800 cl11 cr2)

訪問延遲:

b6a8fd20-e032-11eb-9e57-12bb97331649.png

數(shù)據(jù)來源:https://www.7-cpu.com/cpu/Haswell.html

由此我們可以知道,我們應(yīng)該盡可能追求cache的命中率高,以避免延遲,最好是低級cache的命中率越高越好。

CACHE的組織

現(xiàn)代的cache基本按照這個模式來組織:SET、WAY、TAG、INDEX,這幾個概念是理解Cache的關(guān)鍵。隨便打開一個數(shù)據(jù)手冊,就可以看到這樣的字眼:

b6ba8f2c-e032-11eb-9e57-12bb97331649.png

翻譯成中文就是4路(way)組(set)相聯(lián),VIPT表現(xiàn)為(behave as)PIPT --這尼瑪什么鬼?,cacheline的長度是64字節(jié)。

下面我們來想象一個16KB大小的cache,假設(shè)是4路組相聯(lián),cacheline的長度是64字節(jié)。Cacheline的概念比較簡單,cache的整個替換是以行為單位的,一行64個字節(jié)里面讀了任何一個字節(jié),其實(shí)整個64字節(jié)就進(jìn)入了cache。

比如下面兩段程序,前者的計(jì)算量是后者的8倍:

b6c7d150-e032-11eb-9e57-12bb97331649.png

但是它的執(zhí)行時間,則遠(yuǎn)遠(yuǎn)不到后者的8倍:

b6e07958-e032-11eb-9e57-12bb97331649.png

16KB的cache是4way的話,每個set包括4*64B,則整個cache分為16KB/64B/4 = 64set,也即2的6次方。當(dāng)CPU從cache里面讀數(shù)據(jù)的時候,它會用地址位的BIT6-BIT11來尋址set,BIT0-BIT5是cacheline內(nèi)的offset。

比如CPU訪問地址

0 000000 XXXXXX

或者

1 000000 XXXXXX

或者

YYYY 000000 XXXXXX

由于它們紅色的6位都相同,所以他們?nèi)慷紩业降?個set的cacheline。第0個set里面有4個way,之后硬件會用地址的高位如0,1,YYYY作為tag,去檢索這4個way的tag是否與地址的高位相同,而且cacheline是否有效,如果tag匹配且cacheline有效,則cache命中。

所以地址YYYYYY000000XXXXXX全部都是找第0個set,YYYYYY000001XXXXXX全部都是找第1個set,YYYYYY111111XXXXXX全部都是找第63個set。每個set中的4個way,都有可能命中。

中間紅色的位就是INDEX,前面YYYY這些位就是TAG。具體的實(shí)現(xiàn)可以是用虛擬地址或者物理地址的相應(yīng)位做TAG或者INDEX。如果用虛擬地址做TAG,我們叫VT;如果用物理地址做TAG,我們叫PT;如果用虛擬地址做INDEX,我們叫VI;如果用物理地址做TAG,我們叫PT。工程中碰到的cache可能有這么些組合:

VIVT、VIPT、PIPT。

VIVT的硬件實(shí)現(xiàn)開銷最低,但是軟件維護(hù)成本高;PIPT的硬件實(shí)現(xiàn)開銷最高,但是軟件維護(hù)成本最低;VIPT介于二者之間,但是有些硬件是VIPT,但是behave as PIPT,這樣對軟件而言,維護(hù)成本與PIPT一樣。

在VIVT的情況下,CPU發(fā)出的虛擬地址,不需要經(jīng)過MMU的轉(zhuǎn)化,直接就可以去查cache。

而在VIPT和PIPT的場景下,都涉及到虛擬地址轉(zhuǎn)換為物理地址后,再去比對cache的過程。VIPT如下:

b702cefe-e032-11eb-9e57-12bb97331649.png

PIPT如下:

b70baace-e032-11eb-9e57-12bb97331649.png

從圖上看起來,VIVT的硬件實(shí)現(xiàn)效率很高,不需要經(jīng)過MMU就可以去查cache了。不過,對軟件來說,這是個災(zāi)難。因?yàn)閂IVT有嚴(yán)重的歧義和別名問題。

歧義:一個虛擬地址先后指向兩個(或者多個)物理地址

別名:兩個(或者多個)虛擬地址同時指向一個物理地址

這里我們重點(diǎn)看別名問題。比如2個虛擬地址對應(yīng)同一個物理地址,基于VIVT的邏輯,無論是INDEX還是TAG,2個虛擬地址都是可能不一樣的(盡管他們的物理地址一樣,但是物理地址在cache比對中完全不摻和),這樣它們完全可能在2個cacheline同時命中。

由于2個虛擬地址指向1個物理地址,這樣CPU寫過第一個虛擬地址后,寫入cacheline1。CPU讀第2個虛擬地址,讀到的是過時的cacheline2,這樣就出現(xiàn)了不一致。所以,為了避免這種情況,軟件必須寫完虛擬地址1后,對虛擬地址1對應(yīng)的cache執(zhí)行clean,對虛擬地址2對應(yīng)的cache執(zhí)行invalidate。

而PIPT完全沒有這樣的問題,因?yàn)闊o論多少虛擬地址對應(yīng)一個物理地址,由于物理地址一樣,我們是基于物理地址去尋找和比對cache的,所以不可能出現(xiàn)這種別名問題。

那么VIPT有沒有可能出現(xiàn)別名呢?答案是有可能,也有可能不能。如果VI恰好對于PI,就不可能,這個時候,VIPT對軟件而言就是PIPT了:

VI=PI

PT=PT

那么什么時候VI會等于PI呢?這個時候我們來回憶下虛擬地址往物理地址的轉(zhuǎn)換過程,它是以頁為單位的。假設(shè)一頁是4K,那么地址的低12位虛擬地址和物理地址是完全一樣的。回憶我們前面的地址:

YYYYY000000XXXXXX

其中紅色的000000是INDEX。在我們的例子中,紅色的6位和后面的XXXXXX(cache內(nèi)部偏移)加起來正好12位,所以這個000000經(jīng)過虛實(shí)轉(zhuǎn)換后,其實(shí)還是000000的,這個時候VI=PI,VIPT沒有別名問題。

我們原先假設(shè)的cache是:16KB大小的cache,假設(shè)是4路組相聯(lián),cacheline的長度是64字節(jié),這樣我們正好需要紅色的6位來作為INDEX。但是如果我們把cache的大小增加為32KB,這樣我們需要 32KB/4/64B=128=2^7,也即7位來做INDEX。

YYYY0000000XXXXXX

這樣VI就可能不等于PI了,因?yàn)榧t色的最高位超過了2^12的范圍,完全可能出現(xiàn)如下2個虛擬地址,指向同一個物理地址:

b74163a8-e032-11eb-9e57-12bb97331649.png

這樣就出現(xiàn)了別名問題,我們在工程里,可能可以通過一些辦法避免這種別名問題,比如軟件在建立虛實(shí)轉(zhuǎn)換的時候,把虛實(shí)轉(zhuǎn)換往2^13而不是2^12對齊,讓物理地址的低13位而不是低12位與物理地址相同。

這樣強(qiáng)行繞開別名問題,下圖中,2個虛擬地址指向了同一個物理地址,但是它們的INDEX是相同的,這樣VI=PI,就繞開了別名問題。這通常是PAGE COLOURING技術(shù)中的一種技巧。

b74c9b38-e032-11eb-9e57-12bb97331649.png

如果這種PAGE COLOURING的限制對軟件仍然不可接受,而我們又想享受VIPT的INDEX不需要經(jīng)過MMU虛實(shí)轉(zhuǎn)換的快捷?有沒有什么硬件技術(shù)來解決VIPT別名問題呢?確實(shí)是存在的,現(xiàn)代CPU很多都是把L1 CACHE做成VIPT,但是表現(xiàn)地(behave as)像PIPT。這是怎么做到的呢?

這要求VIPT的cache,硬件上具備alias detection的能力。比如,硬件知道YYYY0000000XXXXXX既有可能出現(xiàn)在第0000000,又可能出現(xiàn)在1000000這2個set,然后硬件自動去比對這2個set里面是否出現(xiàn)映射到相同物理地址的cacheline,并從硬件上解決好別名同步,那么軟件就完全不用操心了。

下面我們記住一個簡單的規(guī)則:

對于VIPT,如果cache的size除以WAY數(shù),小于等于1個page的大小,則天然VI=PI,無別名問題;

對于VIPT,如果cache的size除以WAY數(shù),大于1個page的大小,則天然VI≠PI,有別名問題;這個時候又分成2種情況:

硬件不具備alias detection能力,軟件需要pagecolouring;

硬件具備alias detection能力,軟件把cache當(dāng)成PIPT用。

比如cache大小64KB,4WAY,PAGE SIZE是4K,顯然有別名問題;這個時候,如果cache改為16WAY,或者PAGE SIZE改為16K,不再有別名問題。為什么?感覺小學(xué)數(shù)學(xué)知識也能算得清

CACHE的一致性

Cache的一致性有這么幾個層面

1. 一個CPU的icache和dcache的同步問題

2. 多個CPU各自的cache同步問題

3. CPU與設(shè)備(其實(shí)也可能是個異構(gòu)處理器,不過在Linux運(yùn)行的CPU眼里,都是設(shè)備,都是DMA)的cache同步問題

先看一下ICACHE和DCACHE同步問題。由于程序的運(yùn)行而言,指令流的都流過icache,而指令中涉及到的數(shù)據(jù)流經(jīng)過dcache。所以對于自修改的代碼(Self-Modifying Code)而言,比如我們修改了內(nèi)存p這個位置的代碼(典型多見于JIT compiler),這個時候我們是通過store的方式去寫的p,所以新的指令會進(jìn)入dcache。但是我們接下來去執(zhí)行p位置的指令的時候,icache里面可能命中的是修改之前的指令。

所以這個時候軟件需要把dcache的東西clean出去,然后讓icache invalidate,這個開銷顯然還是比較大的。

但是,比如ARM64的N1處理器,它支持硬件的icache同步,詳見文檔:The Arm Neoverse N1 Platform: Building Blocks for the Next-Gen Cloud-to-Edge Infrastructure SoC

特別注意畫紅色的幾行。軟件維護(hù)的成本實(shí)際很高,還涉及到icache的invalidation向所有核廣播的動作。

接下來的一個問題就是多個核之間的cache同步。下面是一個簡化版的處理器,CPU_A和B共享了一個L3,CPU_C和CPU_D共享了一個L3。實(shí)際的硬件架構(gòu)由于涉及到NUMA,會比這個更加復(fù)雜,但是這個圖反映層級關(guān)系是足夠了。

比如CPU_A讀了一個地址p的變量?CPU_B、C、D又讀,難道B,C,D又必須從RAM里面經(jīng)過L3,L2,L1再讀一遍嗎?這個顯然是沒有必要的,在硬件上,cache的snooping控制單元,可以協(xié)助直接把CPU_A的p地址cache拷貝到CPU_B、C和D的cache。

這樣A-B-C-D都得到了相同的p地址的棕色小球。

假設(shè)CPU B這個時候,把棕色小球?qū)懗杉t色,而其他CPU里面還是棕色,這樣就會不一致了:

b7b878e4-e032-11eb-9e57-12bb97331649.png

這個時候怎么辦?這里面顯然需要一個協(xié)議,典型的多核cache同步協(xié)議有MESI和MOESI。MOESI相對MESI有些細(xì)微的差異,不影響對全局的理解。下面我們重點(diǎn)看MESI協(xié)議。

MESI協(xié)議定義了4種狀態(tài):

M(Modified): 當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)已被修改而且與內(nèi)存中的數(shù)據(jù)不一致,數(shù)據(jù)只在當(dāng)前cache里存在;類似RAM里面是棕色球,B里面是紅色球(CACHE與RAM不一致),A、C、D都沒有球。

E(Exclusive):當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)只在當(dāng)前cache里存在;類似RAM里面是棕色球,B里面是棕色球(RAM和CACHE一致),A、C、D都沒有球。

S(Shared):當(dāng)前cache的內(nèi)容有效,數(shù)據(jù)與內(nèi)存中的數(shù)據(jù)一致,數(shù)據(jù)在多個cache里存在。類似如下圖,在CPU A-B-C里面cache的棕色球都與RAM一致。

I(Invalid): 當(dāng)前cache無效。前面三幅圖里面cache沒有球的那些都是屬于這個情況。

然后它有個狀態(tài)機(jī)

這個狀態(tài)機(jī)比較難記,死記硬背是記不住的,也沒必要記,它講的cache原先的狀態(tài),經(jīng)過一個硬件在本cache或者其他cache的讀寫操作后,各個cache的狀態(tài)會如何變遷。所以,硬件上不僅僅是監(jiān)控本CPU的cache讀寫行為,還會監(jiān)控其他CPU的。只需要記住一點(diǎn):這個狀態(tài)機(jī)是為了保證多核之間cache的一致性,比如一個干凈的數(shù)據(jù),可以在多個CPU的cache share,這個沒有一致性問題;但是,假設(shè)其中一個CPU寫過了,比如A-B-C本來是這樣:

b7d58588-e032-11eb-9e57-12bb97331649.png

然后B被寫過了:

b7f6d9cc-e032-11eb-9e57-12bb97331649.png

這樣A、C的cache實(shí)際是過時的數(shù)據(jù),這是不允許的。這個時候,硬件會自動把A、C的cache invalidate掉,不需要軟件的干預(yù),A、C其實(shí)變地相當(dāng)于不命中這個球了:

b81710de-e032-11eb-9e57-12bb97331649.png

這個時候,你可能會繼續(xù)問,如果C要讀這個球呢?它目前的狀態(tài)在B里面是modified的,而且與RAM不一致,這個時候,硬件會把紅球clean,然后B、C、RAM變地一致,B、C的狀態(tài)都變化為S(Shared):

b82173e4-e032-11eb-9e57-12bb97331649.png

這一系列的動作雖然由硬件完成,但是對軟件而言不是免費(fèi)的,因?yàn)樗馁M(fèi)了時間。如果編程的時候不注意,引起了硬件的大量cache同步行為,則程序的效率可能會急劇下降。

為了讓大家直觀感受到這個cache同步的開銷,下面我們寫一個程序,這個程序有2個線程,一個寫變量,一個讀變量:

b82b739e-e032-11eb-9e57-12bb97331649.png

這個程序里,x和y都是cacheline對齊的,這個程序的thread1的寫,會不停地與thread2的讀,進(jìn)行cache同步。

它的執(zhí)行時間為:

$ time 。/a.out real 0m3.614suser 0m7.021ssys 0m0.004s

它在2個CPU上的userspace共運(yùn)行了7.021秒,累計(jì)這個程序從開始到結(jié)束的對應(yīng)真實(shí)世界的時間是3.614秒(就是從命令開始到命令結(jié)束的時間)。

如果我們把程序改一句話,把thread2里面的c = x改為c = y,這樣2個線程在2個CPU運(yùn)行的時候,讀寫的是不同的cacheline,就沒有這個硬件的cache同步開銷了:

b8382d82-e032-11eb-9e57-12bb97331649.png

它的運(yùn)行時間:

$ time 。/b.out real 0m1.820suser 0m3.606ssys 0m0.008s

現(xiàn)在只需要1.8秒,幾乎減小了一半。

感覺前面那個a.out,雙核的幫助甚至都不大。如果我們改為單核跑呢?

$ time taskset -c 0 。/a.out real 0m3.299suser 0m3.297ssys 0m0.000s

它單核跑,居然只需要3.299秒跑完,而雙核跑,需要3.614s跑完。單核跑完這個程序,甚至比雙核還快,有沒有驚掉下巴??。。∫?yàn)閱魏死锩鏇]有cache同步的開銷。

下一個cache同步的重大問題,就是設(shè)備與CPU之間。如果設(shè)備感知不到CPU的cache的話(下圖中的紅色數(shù)據(jù)流向不經(jīng)過cache),這樣,做DMA前后,CPU就需要進(jìn)行相關(guān)的cacheclean和invalidate的動作,軟件的開銷會比較大。

b843a0c2-e032-11eb-9e57-12bb97331649.png

這些軟件的動作,若我們在Linux編程的時候,使用的是streaming DMA APIs的話,都會被類似這樣的API自動搞定:

dma_map_single()dma_unmap_single()dma_sync_single_for_cpu()dma_sync_single_for_device()dma_sync_sg_for_cpu()dma_sync_sg_for_device()

如果是使用的dma_alloc_coherent() API呢,則設(shè)備和CPU之間的buffer是cache一致的,不需要每次DMA進(jìn)行同步。對于不支持硬件cache一致性的設(shè)備而言,很可能dma_alloc_coherent()會把CPU對那段DMA buffer的訪問設(shè)置為uncachable的。

這些API把底層的硬件差異封裝掉了,如果硬件不支持CPU和設(shè)備的cache同步的話,延時還是比較大的。那么,對于底層硬件而言,更好的實(shí)現(xiàn)方式,應(yīng)該仍然是硬件幫我們來搞定。比如我們需要修改總線協(xié)議,延伸紅線的觸角:

b84d6c2e-e032-11eb-9e57-12bb97331649.png

當(dāng)設(shè)備訪問RAM的時候,可以去snoop CPU的cache:

如果做內(nèi)存到外設(shè)的DMA,則直接從CPU的cache取modified的數(shù)據(jù);

如果做外設(shè)到內(nèi)存的DMA,則直接把CPU的cache invalidate掉。

這樣,就實(shí)現(xiàn)硬件意義上的cache同步。當(dāng)然,硬件的cache同步,還有一些其他方法,原理上是類似的。注意,這種同步仍然不是免費(fèi)的,它仍然會消耗bus cycles的。實(shí)際上,cache的同步開銷還與距離相關(guān),可以說距離越遠(yuǎn),同步開銷越大,比如下圖中A、B的同步開銷比A、C小。

對于一個NUMA服務(wù)器而言,跨NUMA的cache同步開銷顯然是要比NUMA內(nèi)的同步開銷大。

意識到CACHE的編程

通過上一節(jié)的代碼,讀者應(yīng)該意識到了cache的問題不處理好,程序的運(yùn)行性能會急劇下降。所以意識到cache的編程,對程序員是至關(guān)重要的。

從CPU流水線的角度講,任何的內(nèi)存訪問延遲都可以簡化為如下公式:

Average Access Latency = Hit Time + Miss Rate × Miss Penalty

cache miss會導(dǎo)致CPU的stall狀態(tài),從而影響性能?,F(xiàn)代CPU的微架構(gòu)分了frontend和backend。frontend負(fù)責(zé)fetch指令給backend執(zhí)行,backend執(zhí)行依賴運(yùn)算能力和Memory子系統(tǒng)(包括cache)延遲。

backend執(zhí)行中訪問數(shù)據(jù)導(dǎo)致的cache miss會導(dǎo)致backend stall,從而降低IPC(instructions per cycle)。減小cache的miss,實(shí)際上是一個軟硬件協(xié)同設(shè)計(jì)的任務(wù)。比如硬件方面,它支持預(yù)取prefetch,通過分析cache miss的pattern,硬件可以提前預(yù)取數(shù)據(jù),在流水線需要某個數(shù)據(jù)前,提前先取到cache,從而CPU流水線跑到需要它的時候,不再miss。當(dāng)然,硬件不一定有那么聰明,也許它可以學(xué)會一些簡單的pattern。但是,對于復(fù)雜的無規(guī)律的數(shù)據(jù),則可能需要軟件通過預(yù)取指令,來暗示CPU進(jìn)行預(yù)取。

cache預(yù)取

比如在ARM處理器上就有一條指令叫pld,prefetch可以用pld指令:

static inline void prefetch(const void *ptr){ __asm__ __volatile__( “pld %a0” :: “p” (ptr));}

眼見為實(shí),我們隨便從Linux內(nèi)核里面找一個commit:

b87140a4-e032-11eb-9e57-12bb97331649.png

因?yàn)槲覀儚腤iFi收到了一個skb,我們很快就要訪問這個skb里面的數(shù)據(jù)來進(jìn)行packet的分類以及交給IP stack處理了,不如我們先prefetch一下,這樣后面等需要訪問這個skb-》data的時候,流水線可以直接命中cache,從而不打斷。

預(yù)取的原理有點(diǎn)類似今天星期五,咱們在上海office,下周一需要北京分公司的人來上海office開會。于是,我們通知北京office的人周末坐飛機(jī)過來,這樣周一開會的時候就不必等他們了。不預(yù)取的情況下,會議開始后,再等北京的人飛過來,會導(dǎo)致stall狀態(tài)。

任何東西最終還是要落實(shí)到代碼,talk is cheap,show me the code。下面這個是經(jīng)典的二分查找法代碼,這個代碼是網(wǎng)上抄的。

b884d646-e032-11eb-9e57-12bb97331649.png

特別留意ifdef DO_PREFETCH包著的代碼,它提前預(yù)取了下次的中間值。我們來對比下,不預(yù)取和預(yù)取情況下,這個同樣的代碼執(zhí)行時間的差異。先把cpufreq的影響盡可能關(guān)閉掉,設(shè)置為performance:

barry@barry-HP-ProBook-450-G7:~$ sudo cpupower frequency-set --governor performanceSetting cpu: 0Setting cpu: 1Setting cpu: 2Setting cpu: 3Setting cpu: 4Setting cpu: 5Setting cpu: 6Setting cpu: 7

然后我們來對比差異:

b8c6c0c4-e032-11eb-9e57-12bb97331649.png

開啟prefetch執(zhí)行時間大約10s, 不prefetch的情況下,11.6s執(zhí)行完成,性能提升大約14%,所以周末坐飛機(jī)太重要了!

現(xiàn)在我們來通過基于perf的pmu-tools(下載地址:https://github.com/andikleen/pmu-tools),對上面的程序進(jìn)行topdown分析,分析的時候,為了盡可能減小其他因子的影響,我們把程序通過taskset運(yùn)行到CPU0。

先看不prefetch的情況,很明顯,程序是backend_bound的,其中DRAM_Bound占比大,達(dá)到75.8%。

b8d040d6-e032-11eb-9e57-12bb97331649.png

開啟prefetch的情況呢?程序依然是backend_bound的,其中,backend bound的主體依然是DRAM_Bound,但是比例縮小到了60.7%。

b984932e-e032-11eb-9e57-12bb97331649.png

DRAM_Bound主要對應(yīng)cycle_activity.stalls_l3_miss事件,我們通過perf stat來分別進(jìn)行搜集:

b9aa9678-e032-11eb-9e57-12bb97331649.png

我們看到,執(zhí)行prefetch情況下,指令的條數(shù)明顯多了,但是它的insn per cycle變大了,所以總的時間cycles反而減小。其中最主要的原因是cycle_activity.stalls_l3_miss變小了很多次。

這個時候,我們可以進(jìn)一步通過錄制mem_load_retired.l3_miss來分析究竟代碼哪里出了問題,先看noprefetch情況:

ba4cee50-e032-11eb-9e57-12bb97331649.png

焦點(diǎn)在main函數(shù):

ba5757be-e032-11eb-9e57-12bb97331649.png

繼續(xù)annotate一下:

ba61d1e4-e032-11eb-9e57-12bb97331649.png

明顯問題出在array[mid] 《 key這句話這里。做prefetch的情況下呢?

ba751132-e032-11eb-9e57-12bb97331649.png

main的占比明顯變小了(99.93% -》 80.00%):

ba9111de-e032-11eb-9e57-12bb97331649.png

繼續(xù)annotate一下:

ba9a37a0-e032-11eb-9e57-12bb97331649.png

熱點(diǎn)被分散了,預(yù)取緩解了Memory_Bound的情況。

避免false sharing

前面我們提到過,數(shù)據(jù)如果在一個cacheline,被多核訪問的時候,多核間運(yùn)行的cache一致性協(xié)議,會導(dǎo)致cacheline在多核間的同步。這個同步會有很大的延遲,是工程里著名的false sharing問題。

比如下面一個結(jié)構(gòu)體

struct s{ int a; int b;}

如果1個線程讀寫a,另外一個線程讀寫b,那么兩個線程就有機(jī)會在不同的核,于是產(chǎn)生cacheline同步行為的來回顛簸。但是,如果我們把a(bǔ)和b之間padding一些區(qū)域,就可以把這兩個纏繞在一起的人拉開:

struct s{ int a; char padding[cacheline_size - sizeof(int)]; int b;}

因此,在實(shí)際的工程中,我們經(jīng)??吹接腥藢?shù)據(jù)的位置進(jìn)行移位,或者在2個可能引起false sharing的數(shù)據(jù)間填充數(shù)據(jù)進(jìn)行padding。這樣的代碼在內(nèi)核不甚枚舉,我們隨便找一個:

bac48d8e-e032-11eb-9e57-12bb97331649.png

它特別提到在tw_count后面60個字節(jié)(L1_CACHE_BYTES - sizeof(atomic_t))的padding,從而避免false sharing:

bad5263a-e032-11eb-9e57-12bb97331649.png

下面這個則是通過移動結(jié)構(gòu)體內(nèi)部成員的位置,相關(guān)數(shù)據(jù)的cacheline分開的:

bb1e909a-e032-11eb-9e57-12bb97331649.png

這個改動有明顯的性能提升,最高可達(dá)9.9%。代碼里面也有明顯地注釋,usage和parent原先靠地太近,一個頻繁寫,一個頻繁讀。移開了2邊互相不打架了:

bb34ff24-e032-11eb-9e57-12bb97331649.png

把理論和代碼能對上的感覺真TNND爽。無論是996,還是007,都必須留些時間來思考,來讓理論和實(shí)踐結(jié)合,否則,就變成漫無目的的內(nèi)卷,這樣一定會卷輸?shù)?。?nèi)卷并不可悲,可悲的是卷不贏別人。

編輯:jq

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4723

    瀏覽量

    68236

原文標(biāo)題:宋寶華:深入理解cache對寫好代碼至關(guān)重要

文章出處:【微信號:gh_6fde77c41971,微信公眾號:FPGA干貨】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Cache和內(nèi)存有什么區(qū)別

    Cache(高速緩存)和內(nèi)存(Memory,通常指主存儲器或RAM)是計(jì)算機(jī)存儲系統(tǒng)中兩個重要的組成部分,它們在計(jì)算機(jī)的性能和數(shù)據(jù)處理中扮演著不同的角色。以下是對Cache和內(nèi)存之間區(qū)別的詳細(xì)解析。
    的頭像 發(fā)表于 09-26 15:28 ?794次閱讀

    解析Arm Neoverse N2 PMU事件L2D_CACHE_WR

    客戶希望我們幫忙分析 Eigen gemm 基準(zhǔn)測試的一些執(zhí)行情況。具體來說是為什么 L1D_CACHE_WR 的值會低于 L2D_CACHE_WR,這種情況令人費(fèi)解。
    的頭像 發(fā)表于 09-03 11:42 ?1134次閱讀
    解析Arm Neoverse N2 PMU事件L2D_<b class='flag-5'>CACHE</b>_WR

    keil軟件怎么運(yùn)行寫好的程序

    Keil軟件是一款功能強(qiáng)大的嵌入式開發(fā)工具,廣泛應(yīng)用于單片機(jī)、ARM、DSP等嵌入式系統(tǒng)的開發(fā)。本文將介紹如何使用Keil軟件運(yùn)行寫好的程序。 安裝Keil軟件 首先,需要從Keil官網(wǎng)下載Keil
    的頭像 發(fā)表于 09-02 10:29 ?1327次閱讀

    請問lmh6554/6552只畫雙層板能滿足需要嗎?

    layout example好像給的是六層板的示例。。新手還不會畫那么復(fù)雜的,想問兩層板設(shè)計(jì)能滿足一般需要嗎 器件layout參考datasheet里的來,應(yīng)該不會出什么問題吧?
    發(fā)表于 08-27 08:10

    150A300A大電流接線端子對設(shè)備連接的重要

    德索工程師說道在我們的日常生活中,電力設(shè)備無處不在,從家庭電器到工業(yè)機(jī)械,都離不開電力的驅(qū)動。而電力設(shè)備的正常運(yùn)行,離不開一個重要的小部件——150A300A大電流接線端子??赡苡腥藭枺@么一個小小的部件,真的有這么重要嗎?答
    的頭像 發(fā)表于 07-19 15:06 ?333次閱讀
    150A300A大電流接線端子對設(shè)備連接的<b class='flag-5'>重要</b>性

    CortexR52內(nèi)核Cache的具體操作

    本節(jié)內(nèi)容主要講述CortexR52內(nèi)核Cache的具體操作包括使緩存無效(invalidate)操作,清除(clean)緩存。有的時候客戶可能需要對cache做一些清理,比如invalidate
    的頭像 發(fā)表于 07-15 10:32 ?1123次閱讀
    CortexR52內(nèi)核<b class='flag-5'>Cache</b>的具體操作

    什么是虛擬機(jī)?虛擬機(jī)真的那么好用嗎?

    計(jì)算等多個領(lǐng)域。即使目前你還不了解它的應(yīng)用,你應(yīng)該也聽過虛擬機(jī)的大名。那么虛擬機(jī)到底是什么?虛擬機(jī)真的那么好用嗎?讓我們一起揭開虛擬機(jī)的神秘面紗,走進(jìn)這個充滿無限可
    的頭像 發(fā)表于 07-06 08:05 ?463次閱讀
    什么是虛擬機(jī)?虛擬機(jī)<b class='flag-5'>真的</b><b class='flag-5'>那么</b>好用嗎?

    在項(xiàng)目“backup_fw” 中手動設(shè)定application img地址要嗎

    在項(xiàng)目“backup_fw” 中手動設(shè)定applicationimg 地址要嗎,還是不需要勾選,若勾選了,這個地址的值是根據(jù)什么設(shè)定的,謝謝!
    發(fā)表于 06-03 06:44

    逆變電源是交流是直流重要嗎,深循環(huán)電池

    與任何設(shè)備一樣,了解其工作原理才能安全使用它非常重要。電源逆變器上有輸入和輸出端口,用于正確連接和連接電器。如果出現(xiàn)錯誤,您的逆變器將無法工作,甚至?xí)斐砂踩[患。您想知道逆變器是交流還是直流
    的頭像 發(fā)表于 05-29 09:15 ?575次閱讀
    逆變電源是交流是直流<b class='flag-5'>重要嗎</b>,深循環(huán)電池

    代碼檢查的方式三種

    等方面的問題。那么代碼檢查服務(wù)中,提到的編程規(guī)范,規(guī)則集,規(guī)則,規(guī)則用例(場景、誤報(bào)、檢出)分別代表什么意思呢? 編程規(guī)范 在 SAST 靜態(tài)代碼檢查領(lǐng)域, 編程規(guī)范是一套在組織層面關(guān)于代碼
    的頭像 發(fā)表于 02-25 10:08 ?798次閱讀
    <b class='flag-5'>代碼</b>檢查的方式<b class='flag-5'>有</b>三種

    如何寫出好的代碼?高質(zhì)量代碼的三要素

    膾炙人口的詩"春百花秋月,夏涼風(fēng)冬雪",意境唯美,簡明易懂。好的代碼也是讓人陶醉的,那么
    的頭像 發(fā)表于 01-05 11:29 ?1161次閱讀
    如何寫出好的<b class='flag-5'>代碼</b>?高質(zhì)量<b class='flag-5'>代碼</b>的三要素

    線路板金手指,真的金嗎?

    線路板金手指,真的金嗎?
    的頭像 發(fā)表于 12-21 11:07 ?1048次閱讀

    buffer和cache的區(qū)別

    buffer和cache的區(qū)別 緩沖區(qū)(Buffer)和緩存(Cache)是計(jì)算機(jī)系統(tǒng)中用于提高數(shù)據(jù)讀寫效率的兩個關(guān)鍵概念,它們雖然功能有所重疊,但在實(shí)際應(yīng)用中存在一些差異。在下文中,將詳盡、詳實(shí)
    的頭像 發(fā)表于 12-07 11:00 ?782次閱讀

    過孔是什么?過孔哪些種類?PCB上那么密集的過孔是怎么排列的?

    過孔是什么?過孔哪些種類?PCB上那么密集的過孔是怎么排列的? 過孔(Via Hole)是印刷電路板(Printed Circuit Board,PCB)制造中的一種重要元件。它是通過板層內(nèi)的金屬
    的頭像 發(fā)表于 11-30 14:44 ?4159次閱讀

    Chiplet真的那么重要嗎?Chiplet是如何改變半導(dǎo)體的呢?

    2019年以來,半導(dǎo)體行業(yè)逐漸轉(zhuǎn)向新的芯片設(shè)計(jì)理念:chiplet 。從表面上看,這似乎是一個相當(dāng)小的變化,因?yàn)檎嬲l(fā)生的只是芯片被分成更小的部分。
    的頭像 發(fā)表于 11-27 10:48 ?830次閱讀