資料介紹
來源:玩轉單片機
回到最初的問題,CPU是怎樣訪問內存的?簡單的答案是,CPU執行一條訪存指令,把讀寫請求發往內存管理單元。內存管理單元進行虛實轉換,把命令發往總線。總線把命令傳遞給內存控制器,內存控制器再次翻譯地址,對相應內存顆粒進行存取。之后,讀取的數據或者寫入確認按照原路返回。再復雜些,當中插入多級緩存,在每一層緩存都未命中的情況下,訪問才會最終達到內存顆粒。
知道了完整的路徑,那我們開始研究每一步中的硬件到底是怎么樣的,讀寫指令到底是怎樣在其中傳輸的。要了解硬件,首先要說下處理器。處理器的基本結構并不復雜,一般分為取指令,譯碼,發射,執行,寫回五個步驟。而我們說的訪存,指的是訪問數據,不是指令抓取。訪問數據的指令在前三步沒有什么特殊,在第四步,它會被發送到存取單元,等待完成。當指令在存取單元里的時候,產生了一些有趣的問題。
第一個問題,對于讀指令,當處理器在等待數據從緩存或者內存返回的時候,它到底是什么狀態?是等在那不動呢,還是繼續執行別的指令?
一般來說,如果是亂序執行的處理器,那么可以執行后面的指令,如果是順序執行,那么會進入停頓狀態,直到讀取的數據返回。當然,這也不是絕對的。在舉反例之前,我們先要弄清什么是亂序執行。亂序執行是說,對于一串給定的指令,為了提高效率,處理器會找出非真正數據依賴的指令,讓他們并行執行。但是,指令執行結果在寫回到寄存器的時候,必須是順序的。也就是說,哪怕是先被執行的指令,它的運算結果也是按照指令次序寫回到最終的寄存器的。這個和很多程序員理解的亂序執行是有區別的。我發現有些人在調試軟件問題的時候,會覺得使用了一個亂序的處理器,那么可能會使得后面的代碼先被執行,從而讓調試無法進行。他們搞混了兩個個概念,就是訪存次序和指令完成次序。對于普通的運算指令,他們僅僅在處理器內部執行,所以你看到的是寫回次序。而對于訪存指令,指令會產生讀請求,并發送到處理器外部,你看到的次序是訪存次序。對于亂序處理器,可能同時存在多個請求,而其次序,是打亂的,不按原指令順序的。但是此時,這些被發送到外部的讀請求,并沒有拿到返回結果,指令也沒有完成。所以,這并不違反亂序執行順序完成的原則。如果有前后兩條讀指令,沒有數據相關性,哪怕是后面那條讀的數據先被返回,它的結果也不能先寫回到最終的寄存器,而是必須等到前一條完成后才可以。
對于順序執行的處理器,同樣是兩條讀指令,一般必須等到前一條指令完成,才能執行第二條,所以在處理器外部看到的是按次序的訪問。不過也有例外,比如讀寫同時存在的時候,由于讀和寫指令實際上走的是兩條路徑,所以可能會看到同時存在。這個問題在引入更詳細的硬件結構之后再展開。
還有,順序處理器上,哪怕是兩條讀指令,也有可能同時存在兩個外部請求。比如Cortex-A7,對于連續的讀指令,在前一條讀未命中一級緩存,到下一級緩存或者內存抓取數據的時候,第二條讀指令可以被執行。所以說,亂序和順序并不直接影響指令執行次序。他們的區別在于,亂序需要額外的緩沖和邏輯塊(稱為重排序緩沖, re-order buffer)來計算和存儲指令間的相關性以及執行狀態。而順序處理器沒有重排序緩沖,或者非常簡單。這些額外的面積可不小,據我所看到的,可以占到處理器核心的40%。它們所帶來的更高的并行度,性能提升卻未必有40%。因為我們寫的單線程程序,由于存在很多數據相關,造成指令的并行是有限的,再大的重排序緩沖也解決不了真正的數據相關。所以對于功耗敏感的處理器還是使用順序執行。
還有一點需要注意,順序執行的處理器,在指令抓取,解碼和發射階段,兩條或者多條指令,是可以同時進行的。比如,無依賴關系的讀指令和運算指令,可以被同時發射到不同的執行單元,同時開始執行。但是完成還是按順序的。
但是,在有些ARM處理器上,比如Cortex-A53,向量或者加解密指令是可以亂序完成的,這類運算的結果之間并沒有數據依賴性。這點請千萬注意。
再來看看寫指令。寫和讀有個很大的不同,就是寫指令不必等待數據寫到緩存或者內存,就可以完成了。寫出去的數據會到一個叫做store buffer的緩沖,它位于一級緩存之前,只要它沒滿,處理器就可以直接往下走,不必停止并等待。所以,對于連續的寫指令,無論順序還是亂序執行處理器,都可能看到多個寫請求同時掛在處理器總線上。同時,由于處理器不必像讀指令那樣等待結果,就可以在單位時間內送出更多寫請求,所以我們可以看到寫帶寬通常是大于讀帶寬的。
以上所說的讀寫訪問都是在開啟緩存的情況,關閉的情況以后討論。
對于同時存在的多個請求,有一個名詞來定義它,叫做outstanding transaction,簡稱OT。它和延遲一起,構成了我們對訪存性能的描述。延遲這個概念,在不同領域有不同的定義。在網絡上,網絡延遲表示單個數據包從本地出發,經過交換和路由,到達對端,然后返回,當中所花的總時間。在處理器上,我們也可以說讀寫的延遲是指令發出,經過緩存,總線,內存控制器,內存顆粒,然后原路返回所花費的時間。但是,更多的時候,我們說的訪存延遲是大量讀寫指令被執行后,統計出來的平均訪問時間。這里面的區別是,當OT=1的時候,總延時是簡單累加。當OT>1,由于同時存在兩個訪存并行,總時間通常少于累加時間,并且可以少很多。這時候得到的平均延遲,也被稱作訪存延遲,并且用得更普遍。再精確一些,由于多級流水線的存在,假設流水線每一個階段都是一個時鐘周期,那訪問一級緩存的平均延遲其實就是一個周期.而對于后面的二級,三級緩存和內存,就讀指令來說,延遲就是從指令被發射(注意,不是從取指)到最終數據返回的時間,因為處理器在執行階段等待,流水線起不了作用。如果OT=2, 那么時間可能縮短將近一半。OT>1的好處在這里就體現出來了。當然,這也是有代價的,存儲未完成的讀請求的狀態需要額外的緩沖,而處理器可能也需要支持亂序執行,造成面積和功耗進一步上升。對于寫指令,只要store buffer沒滿,還是一個時鐘周期。當然,如果流水線上某個節拍大于一個時鐘周期,那平均的延時就會取決于這個最慢的時間。在讀取二級,三級緩存和內存的時候,我們可以把等待返回看作一個節拍,那么就能很自然的理解此時的延遲了。由此,我們可以得到每一級緩存的延遲和訪存延遲。
上圖畫了讀寫指令經過的單元。我把流程簡單描述下:
當寫指令從存取單元LSU出發,它首先經過一個小的store queue,然后進入store buffer。之后,寫指令就可以完成了,處理器不必等待。Store buffer通常由幾個8-16字節的槽位組成,它會對自己收到的每項數據進行地址檢查,如果可以合并就合并,然后發送請求到右邊的一級緩存,要求分配一行緩存,來存放數據,直到收到響應,這稱作寫分配write allocate。當然,等待的過程可以繼續合并同緩存行數據。如果數據是Non-Cacheable的,那么它會計算一個等待時間,然后把數據合并,發送到總線接口單元BIU里面的寫緩沖Write buffer。 而寫緩沖在把數據發到二級緩存之前,會經過監聽控制單元,把四個核的緩存做一致性。過程和總線描述的類似,就不多講了。
當讀指令從存取單元LSU出發,無論是否Cacheable的,都會經過一級緩存。如果命中,那么直接返回數據,讀指令完成。如果未命中,那么Non-Cacheable的請求直接被送到Read Buffer。如果是Cacheable的,那么一級緩存需要分配一個緩存行,并且把原來的數據寫出到替換緩沖eviction buffer,同時發起一個緩存行填充,發送到Linefill Buffer。eviction buffer會把它的寫出請求送到BIU里面的Write buffer,和Store Buffer送過來的數據一起,發到下一級接口。然后這些請求又經過監聽控制單元做一致性檢測后,發到二級緩存。當然有可能讀取的數據存在于別的處理器一級緩存,那么就直接從那里抓取。
過程并不復雜,但程序員關心的是這個過程的瓶頸在哪,對讀寫性能影響如何。我們已經解釋過,對于寫,由于它可以立刻完成,所以它的瓶頸并不來自于存取單元;對于讀,由于處理器會等待,所以我們需要找到讀取路徑每一步能發出多少OT,每個OT的數據長度是多少。
拿Cortex-A7來舉例,它有2x32字節linefill buffer,支持有條件的miss-under-miss(相鄰讀指令必須在3時鐘周期內),也就是OT最多等于2,而它的數據緩存行長度是64字節,所以每個OT都是半個緩存行長度。對于Cacheable的讀來說,我還關心兩個數據,就是eviction buffer和Write buffer,它們總是伴隨著line fill。在A7中,存在一個64字節的eviction buffer和一個Write buffer。有了這些條件,那么我就可以說,對于連續的讀指令,我能做到的OT就是2,而linefill的速度和eviction,write buffer的速度一致,因為2x32=64字節。
那這個結論是不是正確?寫個小程序測試下就知道。我們可以關掉二級緩存,保留一級緩存,然后用以下指令去讀取一個較大的內存區域。所有的地址都是緩存行對齊,對齊的意義我就不說了,不對齊,甚至越過緩存行邊界,會把一個操作變成兩個,肯定會慢。偽代碼如下:
loop
load R0, addr 0
load R0, addr 4
load R0, addr 8
load R0, addr 12
addr=addr 16
這里通過讀取指令不斷地去讀數據。通過處理器自帶的性能計數器看了下一級緩存的未命中率,6%多一點。這恰恰是4/64字節的比率。說明對于一個新的緩存行,第一個四字節總是未命中,而后面15個四字節總是命中。當然,具體的延遲和帶寬還和總線,內存控制器有關,這里只能通過命中率簡單驗證下。
對于有的處理器,是嚴格順序執行的,沒有A7那樣的miss-under-miss機制,所以OT=1。我在Cortex-R5上做同樣的實驗,它的緩存行長度是32字節,2xLinefill buffer是32字節。測試得到的命中率是12%多點。也完全符合估算。
但是為什么R5要設計兩個32字節長度的Linefill buffer?既然它的OT=1,多出來的一個豈不是沒用?實際上它是可以被用到的,而方法就是使用預取指令PLD。預取指令的特點就是,它被執行后,處理器同樣不必等待,而這個讀請求會被同樣發送到一級緩存。等到下次有讀指令來真正讀取同樣的緩存行,那么就可能發現數據已經在那了。它的地址必須是緩存行對齊。這樣,讀也可像寫那樣把第二個 Linefill buffer給用上了。
我們把它用到前面的例子里:
loop
PLD addr 32
load R0, addr 0;...;load R0, addr 28;
load R0, addr 32;...;load R0, addr 60;
addr=addr 64
PLD預先讀取第二行讀指令的地址。測試發現,此時的未命中率還是6%。這也符合估算,因為第二排的讀指令總是命中,第一排的未命中率4/32,平均下就是6%。而測試帶寬提升了80%多。單單看OT=2,它應該提升100%,但實際不可能那么理想化,80%也可以理解。
還有一種機制使得OT可以更大,那就是緩存的硬件預取。當程序訪問連續的或者有規律的地址時,緩存會自動檢測出這種規律,并且預先去把數據取來。這種方法同樣不占用處理器時間,但是也會占用linefill buffer,eviction buffer和write buffer。所以,如果這個規律找的不好,那么反而會降低效率。
讀看完了,那寫呢?Cacheable的寫,如果未命中緩存,就會引發write allocate,繼而造成Linefill和eviction,也就是讀操作。這點可能很多程序員沒想到。當存在連續地址的寫時,就會伴隨著一連串的緩存行讀操作。有些時候,這些讀是沒有意義的。比如在memset函數中,可以直接把數據寫到下一級緩存或者內存,不需要額外的讀。于是,大部分的ARM處理器都實現了一個機制,當探測到連續地址的寫,就不讓store buffer把數據發往一級緩存,而是直接到write buffer。并且,這個時候,更容易合并,形成突發寫,提高效率。在Cortex-A7上它被稱作Read allocate模式,意思是取消了write allocate。而在有的處理器上被稱作streaming模式。很多跑分測試都會觸發這個模式,因此能在跑分上更有優勢。
但是,進入了streaming模式并不意味著內存控制器收到的地址都是連續的。想象一下,我們在測memcpy的時候,首先要從源地址讀數據,發出去的是連續地址,并且是基于緩存行的。過了一段時間后,緩存都被用完,那么eviction出現了,并且它是隨機或者偽隨機的,寫出去的地址并無規律。這就打斷了原本的連續的讀地址。再看寫,在把數據寫到目的地址時,如果連續的寫地址被發現,那么它就不會觸發額外的linefill和eviction。這是好事??墒?,直接寫到下一級緩存或者內存的數據,很有可能并不是完整的緩存發突發寫,應為store buffer也是在不斷和write buffer交互的,而write buffer還要同時接受eviction buffer的請求。其結果就是寫被分成幾個小段。這些小塊的寫地址,eviction的寫地址,混合著讀地址,讓總線和內存控制器增加了負擔。它們必須采用合適的算法和參數,才能合并這些數據,更快的寫到內存顆粒。
然而事情還沒有完。我們剛才提到,streaming模式是被觸發的,同樣的,它也可以退出。退出條件一般是發現存在非緩存行突發的寫。這個可能受write buffer的響應時間影響。退出后,write allocate就又恢復了,從而讀寫地址更加不連續,內存控制器更加難以優化,延時進一步增加,反饋到處理器,就更難保持在streaming模式。
再進一步,streaming模式其實存在一個問題,那就是它把數據寫到了下一級緩存或者內存,萬一這個數據馬上就會被使用呢?那豈不是還得去抓???針對這個問題,在ARM v8指令集中(適用于A53/57/72),又引入了新的一條緩存操作指令DCZVA,可以把整行緩存設成0,并且不引發write allocate。為什么?因為整行數據都被要改了,而不是某個字段被改,那就沒有必要去把原來的值讀出來,所以只需要allocate,不需要讀取,但它還是會引發eviction。類似的,我們也可以在使用某塊緩存前把它們整體清除并無效化,clean&invalidate,這樣就不會有eviction。不過如果測試數據塊足夠大,這樣只是相當于提前做了eviction,并不能消除,讓寫集中在某段。使之后的讀更連續。
以上都是針對一級緩存。二級緩存的控制力度就小些,代碼上無法影響,只能通過設置寄存器,打開二級緩存預取或者設置預取偏移。我在ARM的二級緩存控制器PL301上看到的,如果偏移設置的好,抓到的數據正好被用上,可以在代碼和一級緩存優化完成的基礎上,讀帶寬再提升150%。在新的處理器上,同時可以有多路的預取,探測多組訪存模板,進一步提高效率。并且,每一級緩存后面掛的OT數目肯定大于上一級,它包含了各類讀寫和緩存操作,利用好這些OT,就能提高性能。
對于Non-Cacheable的寫,它會被store buffer直接送到write buffer進行合并,然后到下一級緩存。對于Non-Cacheable的讀,我們說過它會先到緩存看看是不是命中,未命中的話直接到read buffer,合并后發往下一級緩存。它通常不占用linefill buffer,因為它通常是4到8字節,不需要使用緩存行大小的緩沖。
我們有時候也可以利用Non-Cacheable的讀通道,和Cacheable的讀操作并行,提高效率。它的原理就是同時利用linefill buffer和read buffer。此時必須保證處理器有足夠的OT,不停頓。
簡而言之,訪存的軟件優化的原則就是,保持對齊,找出更多可利用的OT,訪存和預取混用,保持更連續的訪問地址,縮短每一環節的延遲。
最后解釋一下緩存延遲的產生原因。程序員可能不知道的是,不同大小的緩存,他們能達到的時鐘頻率是不一樣的。ARM的一級緩存,16納米工藝下,大小在32-64K字節,可以跑在1-2Ghz左右,和處理器同頻。處理器頻率再快,那么訪問緩存就需要2-3個處理器周期了。而二級緩存更慢,256K字節的,能有800Mhz就很好了。這是由于緩存越大,需要查找的目錄index越大,扇出fanout和電容越大,自然就越慢。還有,通常處理器宣傳時候所說的訪問緩存延遲,存在一個前提,就是使用虛擬地址索引VIPT。這樣就不需要查找一級Tlb表,直接得到索引地址。如果使用物理地址索引PIPT,在查找一級tlb進行虛實轉換時,需要額外時間不說,如果產生未命中,那就要到二級甚至軟件頁表去找。那顯然太慢了。那為什么不全使用VIPT呢?因為VIPT會產生一個問題,多個虛地址會映射到一個實地址,從而使得緩存多個表項對應一個實地址。存在寫操作時,多條表項就會引起一致性錯誤。而指令緩存通常由于是只讀的,不存在這個問題。所以指令緩存大多使用VIPT。隨著處理器頻率越來越高,數據緩存也只能使用VIPT。為了解決前面提到的問題,ARM在新的處理器里面加了額外的邏輯來檢測重復的表項。
啰嗦了那么多,該說下真正系統里的訪存延遲到底如何了。直接上圖:
上圖的配置中,DDR4跑在3.2Gbps,總線800Mhz,內存控制器800Mhz,處理器2.25Ghz。關掉緩存,用讀指令測試。延遲包括出和進兩個方向,69.8納秒,這是在總是命中一個內存物理頁的情況下的最優結果,隨機的地址訪問需要把17.5納秒再乘以2到3。關于物理頁的解釋請參看內存一章。
在內存上花的時間是控制器 物理層 接口,總共38.9納秒。百分比55%。如果是訪問隨機地址,那么會超過70納秒,占70%。在總線和異步橋上花的時間是20納秒,8個總線時鐘周期,28%。處理器11.1納秒,占16%,20個處理器時鐘周期。
所以,即使是在3.2Gbps的DDR4上,大部分時間還都是在內存,顯然優化可以從它上面入手。在處理器中的時間只有一小部分。但從另外一個方面,處理器控制著linefill,eviction的次數,地址的連續性,以及預取的效率,雖然它自己所占時間最少,但也是優化的重點。
在ARM的路線圖上,還出現了一項并不算新的技術,稱作stashing。它來自于網絡處理器,原理是外設控制器(PCIe,網卡)向處理器發送請求,把某個數據放到緩存,過程和監聽snooping很類似。在某些領域,這項技術能夠引起質的變化。舉個例子,intel至強處理器,配合它的網絡轉發庫DPDK,可以做到平均80個處理器周期接受從PCIe網卡來的包,解析包頭后送還回去。80周期是個什么概念?看過了上面的訪存延遲圖后你應該有所了解,處理器訪問下內存都需要200-300周期。而這個數據從PCIe口DMA到內存,然后處理器抓取它進行處理后,又經過DMA從PCIe口出去,整個過程肯定大于訪存時間。80周期的平均時間說明它肯定被提前送到了緩存。 但傳進來的數據很多,只有PCIe或者網卡控制器才知道哪個是包頭,才能精確的推送數據,不然緩存會被無用的數據淹沒。這個過程做好了,可以讓軟件處理以太網或者存儲單元的速度超過硬件加速器。事實上,在freescale的網絡處理器上,有了硬件加速器的幫助,處理包的平均延遲需要200處理器周期,已經慢于至強了。
還有,在ARM新的面向網絡和服務器的核心上,會出現一核兩線程的設計。處理包的任務天然適合多線程,而一核兩線程可以更有效的利用硬件資源,再加上stashing,如虎添翼。
(mbbeetchina)
下載該資料的人也在下載
下載該資料的人還在閱讀
更多 >
- ARM筆記:內核物理內存映射區的虛擬內存資料下載
- 汽車蓄電池平時應該怎樣自查?資料下載
- 人眼怎樣感知3D?資料下載
- 說到C語言編程程序的內存是個重要問題,那如何布局呢 ?資料下載
- 你寫出來的C語言是怎樣調用硬件的!資料下載
- 時鐘同步怎樣組網?資料下載
- PCB廠工程人員眼里優秀的PCB文件是怎樣的?資料下載
- 人眼怎樣感知3D?資料下載
- CPU上的晶體管有多少個?資料下載
- 32位CPU中執行單元總體結構資料下載
- 怎樣延長蓄電池的使用壽命資料下載
- 單片機僅靠CPU和內存是無法運行的!資料下載
- 單片機=CPU、內存、外圍功能...如何理解資料下載
- 電容,哪些因素影響發熱?波紋怎樣處理?資料下載
- 機器視覺應用,CPU還是FPGA?資料下載
- EC SRAM映射到CPU Memory空間的共享內存設計 1231次閱讀
- CPU、GPU和內存知識科普 1532次閱讀
- RDMA(遠程直接內存訪問)傳輸協議概述和應用案例 1958次閱讀
- 一文解析CPU、內存、編譯器(編程語言)、操作系統 1168次閱讀
- 組態西門子CPU的訪問保護 1726次閱讀
- CPU、寄存器和內存單元的物理結構 3961次閱讀
- 一文搞懂物理內存組織的體系結構與內存模型 2126次閱讀
- 如何設置 DMA(直接內存訪問) 3618次閱讀
- 緩存如何工作,如何設計CPU緩存 2616次閱讀
- 虛擬機:查看進程內存和CPU占用的方法 8242次閱讀
- IT從業者應該知道的CPU Cache相關的知識 2867次閱讀
- KASAN是如何實現檢測的?如何根據shadow memory的值判斷內存訪問操作是否valid? 9388次閱讀
- 共享內存IPC原理,Linux進程間如何共享內存? 8505次閱讀
- 簡單了解CPU和內存搭配規律 3.3w次閱讀
- 基于SLUB的DEBUG功能,如何幫忙檢測內存越界和訪問已經釋放的內存 9299次閱讀
下載排行
本周
- 1電子電路原理第七版PDF電子教材免費下載
- 0.00 MB | 1489次下載 | 免費
- 2單片機典型實例介紹
- 18.19 MB | 91次下載 | 1 積分
- 3S7-200PLC編程實例詳細資料
- 1.17 MB | 27次下載 | 1 積分
- 4筆記本電腦主板的元件識別和講解說明
- 4.28 MB | 18次下載 | 4 積分
- 5開關電源原理及各功能電路詳解
- 0.38 MB | 9次下載 | 免費
- 6基于AT89C2051/4051單片機編程器的實驗
- 0.11 MB | 4次下載 | 免費
- 7基于單片機和 SG3525的程控開關電源設計
- 0.23 MB | 3次下載 | 免費
- 8基于單片機的紅外風扇遙控
- 0.23 MB | 3次下載 | 免費
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234313次下載 | 免費
- 2PADS 9.0 2009最新版 -下載
- 0.00 MB | 66304次下載 | 免費
- 3protel99下載protel99軟件下載(中文版)
- 0.00 MB | 51209次下載 | 免費
- 4LabView 8.0 專業版下載 (3CD完整版)
- 0.00 MB | 51043次下載 | 免費
- 5555集成電路應用800例(新編版)
- 0.00 MB | 33562次下載 | 免費
- 6接口電路圖大全
- 未知 | 30319次下載 | 免費
- 7Multisim 10下載Multisim 10 中文版
- 0.00 MB | 28588次下載 | 免費
- 8開關電源設計實例指南
- 未知 | 21539次下載 | 免費
總榜
- 1matlab軟件下載入口
- 未知 | 935053次下載 | 免費
- 2protel99se軟件下載(可英文版轉中文版)
- 78.1 MB | 537791次下載 | 免費
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420026次下載 | 免費
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234313次下載 | 免費
- 5Altium DXP2002下載入口
- 未知 | 233045次下載 | 免費
- 6電路仿真軟件multisim 10.0免費下載
- 340992 | 191183次下載 | 免費
- 7十天學會AVR單片機與C語言視頻教程 下載
- 158M | 183277次下載 | 免費
- 8proe5.0野火版下載(中文版免費下載)
- 未知 | 138039次下載 | 免費
評論
查看更多