自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

優(yōu)化代碼性能:利用CPU緩存原理

開發(fā) 前端
在編寫程序時,充分考慮循環(huán)結構對 CPU Cache 命中率的影響,并運用上述優(yōu)化方法,可以顯著提高程序的運行效率 。尤其是在處理大規(guī)模數據和對性能要求苛刻的應用場景中,循環(huán)優(yōu)化是提升程序性能的關鍵環(huán)節(jié)之一 。

在計算機的世界里,有一場如同龜兔賽跑般的速度較量,主角便是 CPU 和內存 。龜兔賽跑的故事大家都耳熟能詳,兔子速度飛快,烏龜則慢吞吞的。在計算機中,CPU 就如同那敏捷的兔子,擁有超高的運算速度,能夠在極短的時間內完成大量復雜的計算任務;而內存則像是那步伐緩慢的烏龜,雖然它能夠存儲程序運行所需的數據和指令,但數據的讀取和寫入速度,與 CPU 相比,簡直是天壤之別。

現代 CPU 的運行頻率可以輕松達到數 GHz,這意味著它每秒能夠執(zhí)行數十億次的操作。而內存的訪問速度,雖然也在不斷提升,但與 CPU 的速度相比,仍然存在著巨大的差距。這種速度上的不匹配,就好比是讓一個短跑冠軍和一個普通人進行接力賽跑,每次短跑冠軍快速跑到終點后,都需要花費大量時間等待普通人慢悠悠地把接力棒送過來,這無疑會極大地影響整個系統的運行效率。

為了解決這個問題,計算機科學家們引入了一種名為 CPU Cache 的高速緩沖存儲器,它就像是在 CPU 和內存之間搭建了一座 “高速橋梁”,讓 CPU 能夠更快地獲取數據,從而提高計算機的整體性能 。接下來,就讓我們一起深入了解一下 CPU Cache 的奧秘吧。

一、CPU Cache概述

你可能會好奇為什么有了內存,還需要 CPU Cache?根據摩爾定律,CPU 的訪問速度每 18 個月就會翻倍,相當于每年增長 60% 左右,內存的速度當然也會不斷增長,但是增長的速度遠小于 CPU,平均每年只增長 7% 左右。于是,CPU 與內存的訪問性能的差距不斷拉大。

到現在,一次內存訪問所需時間是 200~300 多個時鐘周期,這意味著 CPU 和內存的訪問速度已經相差 200~300 多倍了。為了彌補CPU和內存之間的性能差異,以便于能夠真實變得把CPU的性能提升利用起來,而不是讓它在那里空轉,我們在現代CPU中引入了高速緩存。

從CPU Cache被加入到現有的CPU里開始,內存中的指令、數據,會被加載到L1-L3 Cache中,而不是直接從CPU訪問內存中取拿。CPU從內存讀取數據到CPU Cache的過程中,是一小塊一小塊來讀取數據的,而不是按照單個數組元素來讀取數據的。這樣一小塊一小塊的數據,在CPU Cache里面,叫做Cache Line(緩存塊)

在我們日常使用的 Intel 服務器或者 PC 里,Cache Line 的大小通常是 64 字節(jié)。

1.1Cache 的定義與角色

CPU Cache,即高速緩沖存儲器,是位于 CPU 和內存之間的臨時存儲器 。它就像是一個數據 “中轉站”,主要作用是為了加速 CPU 讀取數據的速度 。由于 CPU 的運算速度極快,而內存的讀寫速度相對較慢,這就好比一個短跑冠軍和一個慢跑者,兩者的速度差距明顯。

如果 CPU 直接從內存中讀取數據,就需要花費大量時間等待,這無疑會降低整個計算機系統的運行效率 。而 CPU Cache 的出現,就很好地解決了這個問題。它的速度比內存快很多,能夠提前將 CPU 可能需要的數據從內存中讀取出來并存儲起來,當 CPU 需要數據時,首先會在 Cache 中查找,如果找到了,就可以直接從 Cache 中快速獲取,大大節(jié)省了讀取數據的時間,提高了 CPU 的工作效率 。

1.2Cache 的層級結構

隨著科技發(fā)展,熱點數據的體積越來越大,單純的增加一級緩存大小的性價比已經很低了;二級緩存就是一級緩存的緩沖器:一級緩存制造成本很高因此它的容量有限,二級緩存的作用就是存儲那些CPU處理時需要用到、一級緩存又無法存儲的數據。

CPU Cache 通常分為三級,分別是 L1 Cache(一級緩存)、L2 Cache(二級緩存)和 L3 Cache(三級緩存) 。這三級緩存就像是一個金字塔結構,從 L1 到 L3,速度逐漸變慢,容量逐漸增大 。

L1 Cache 是離 CPU 核心最近的緩存,它的速度最快,幾乎可以與 CPU 的頻率同步運行 。L1 Cache 又可以細分為數據緩存(L1 D - Cache)和指令緩存(L1 I - Cache),分別用于存儲數據和指令 。

數據緩存就像是一個小型的數據倉庫,專門存放 CPU 在運算過程中需要處理的數據;指令緩存則像是一個指令庫,存儲著 CPU 執(zhí)行運算時所需要的指令 。由于 L1 Cache 與CPU 核心緊密相連,訪問速度極快,但其容量相對較小,一般只有幾十 KB 到幾百 KB 。例如,英特爾酷睿 i7 - 13700K 處理器,其每個核心的 L1 數據緩存為 32KB,L1指令緩存也為 32KB 。

L2 Cache 位于 L1 Cache 之后,速度比 L1 Cache 稍慢,但容量比 L1 Cache 大 。它的作用是作為 L1 Cache的補充,當 L1 Cache 中沒有找到 CPU 需要的數據或指令時,CPU 就會到L2 Cache 中查找 。L2 Cache 的容量一般在幾百 KB 到幾 MB 之間 。以剛才提到的酷睿i7 - 13700K 為例,其每個核心的L2緩存為 1MB 。

L3 Cache 是三級緩存中速度最慢但容量最大的緩存,它通常是多個 CPU 核心共享的 。當 L1 和 L2 Cache 都沒有命中時,CPU 會訪問 L3 Cache 。L3 Cache 的存在可以進一步提高數據的命中率,減少 CPU 訪問內存的次數 。它的容量一般在幾 MB 到幾十 MB 之間 。酷睿 i7 - 13700K 的 L3 緩存為 30MB 。

在實際工作中,CPU 按照 L1 Cache、L2 Cache、L3 Cache 的順序依次查找數據 。如果在某一級緩存中找到了所需的數據,就稱為緩存命中;如果在三級緩存中都沒有找到,才會去內存中查找,這就是緩存未命中 。緩存命中的概率越高,CPU 獲取數據的速度就越快,計算機的性能也就越好 。

二、CPU Cache核心原理

2.1局部性原理

CPU Cache 能夠提高數據訪問性能,背后依賴的是局部性原理 。局部性原理又分為時間局部性和空間局部性 。

時間局部性,是指如果一個數據項在某個時刻被訪問,那么在不久的將來它很可能再次被訪問 。就好比我們看電視劇時,喜歡反復觀看精彩的片段。在程序中,循環(huán)結構就是時間局部性的典型體現 。例如下面這段簡單的 C 語言代碼:

int sum = 0;
int arr[100] = {1, 2, 3,..., 100};
for (int i = 0; i < 100; i++) {
    sum += arr[i];
}

在這個循環(huán)中,變量sum會被多次訪問,每次循環(huán)都要讀取和更新它的值 。根據時間局部性原理,CPU Cache 會將sum的值緩存起來,這樣在后續(xù)的循環(huán)中,CPU 就可以直接從 Cache 中快速獲取sum的值,而不需要每次都從內存中讀取 ,大大提高了訪問效率 。

空間局部性,是指如果一個數據項被訪問,那么與其相鄰的數據項很可能在不久的將來也被訪問 。這就像我們在書架上找書,找到一本后,往往會順便看看它旁邊的書 。在計算機中,內存中的數據通常是按順序存儲的 。比如數組,數組元素在內存中是連續(xù)存放的 。當 CPU 訪問數組中的一個元素時,根據空間局部性原理,它很可能在接下來的操作中訪問該元素附近的其他元素 。

還是以上面的代碼為例,當 CPU 訪問arr[i]時,Cache 會把arr[i]以及它附近的一些元素(比如arr[i - 1]、arr[i + 1]等,具體取決于 Cache Line 的大小,后面會詳細介紹 Cache Line)一起緩存起來 。這樣,當 CPU 訪問下一個元素arr[i + 1]時,就有可能直接從 Cache 中獲取,而無需再次訪問內存 。

CPU Cache 正是利用了這兩種局部性原理,提前將可能被訪問的數據從內存中讀取到 Cache 中 。當 CPU 需要數據時,首先在 Cache 中查找,如果命中(即找到所需數據),就可以快速獲取數據,大大縮短了數據訪問時間 。如果未命中,才會去內存中讀取數據,并將讀取到的數據及其相鄰的數據塊存入 Cache,以便后續(xù)訪問 。通過這種方式,CPU Cache 有效地提高了數據訪問性能,減少了 CPU 等待數據的時間,從而提升了整個計算機系統的運行效率 。

2.2緩存命中與未命中

在 CPU 訪問數據的過程中,緩存命中和未命中是兩個非常關鍵的概念,它們對 CPU 的數據讀取流程有著重要的影響 。

當 CPU 需要讀取某個數據時,它會首先在 Cache 中查找該數據 。如果該數據正好存在于 Cache 中,這就稱為緩存命中 。緩存命中是一種非常理想的情況,因為 Cache 的速度比內存快很多,CPU 可以直接從 Cache 中快速獲取數據,幾乎不需要等待 。這就好比你在自己的書架上找一本書,一下子就找到了,馬上就可以開始閱讀 。

在緩存命中的情況下,CPU 的數據讀取流程非常簡單高效,直接從 Cache 中讀取數據并進行后續(xù)的運算操作,大大提高了 CPU 的工作效率 。例如,在一個頻繁訪問數組元素的程序中,如果數組元素被緩存到 Cache 中,當 CPU 再次訪問這些元素時,就會發(fā)生緩存命中,CPU 能夠迅速獲取數據,使得程序能夠快速運行 。

然而,如果 CPU 在 Cache 中沒有找到所需的數據,這就是緩存未命中 。緩存未命中的情況相對復雜,對 CPU 的性能影響也較大 。當緩存未命中時,CPU 不得不從速度較慢的內存中讀取數據 。這就像你在自己的書架上沒找到想要的書,只能去圖書館的書庫中尋找,這無疑會花費更多的時間 。

在從內存讀取數據的過程中,CPU 需要等待內存返回數據,這個等待時間可能會包含多個時鐘周期 。而且,在從內存讀取數據的同時,為了提高后續(xù)數據訪問的命中率,CPU 會將讀取到的數據以及該數據周圍的一部分數據(以 Cache Line 為單位,后面會詳細介紹)存入 Cache 中 。

如果此時 Cache 已滿,還需要采用一定的替換算法(如最近最少使用 LRU 算法等),將 Cache 中不太常用的數據替換出去,為新的數據騰出空間 。例如,在一個處理大數據集的程序中,如果數據量超過了 Cache 的容量,就很容易出現緩存未命中的情況 。每次緩存未命中都需要 CPU 從內存讀取數據,這會導致程序的運行速度明顯下降,因為內存訪問的延遲相對較高 。

緩存命中率是衡量 Cache 性能的重要指標,它表示緩存命中次數在總訪問次數中所占的比例 。緩存命中率越高,說明 CPU 從 Cache 中獲取數據的次數越多,也就意味著 CPU 等待數據的時間越短,計算機系統的性能也就越好 。因此,在計算機系統設計和程序優(yōu)化中,提高緩存命中率是一個重要的目標 。通過合理地利用局部性原理、優(yōu)化數據訪問模式以及選擇合適的 Cache 大小和替換算法等方法,可以有效地提高緩存命中率,減少緩存未命中的次數,從而提升計算機系統的整體性能 。

2.3Cache Line

Cache Line 是 CPU Cache 中的一個重要概念,它是 CPU 與內存之間進行數據傳輸的最小單位 。簡單來說,當 CPU 需要從內存讀取數據到 Cache 時,并不是以單個字節(jié)為單位進行讀取,而是一次性讀取一個固定大小的數據塊,這個數據塊就是一個 Cache Line 。

Cache Line 的大小通常是固定的,常見的 Cache Line 大小有 32 字節(jié)、64 字節(jié)等 。不同的 CPU 架構可能會有不同的 Cache Line 大小 。例如,在許多現代 x86 架構的 CPU 中,Cache Line 的大小一般為 64 字節(jié) 。Cache Line 的存在主要是為了利用空間局部性原理,提高數據讀取的效率 。當 CPU 訪問某個內存地址時,雖然它只需要該地址處的一個數據,但由于空間局部性,與該地址相鄰的數據很可能也會被訪問 。因此,將該地址所在的一整段數據(即一個 Cache Line)都讀取到 Cache 中,可以減少后續(xù)數據訪問時的緩存未命中次數 。

舉個例子,假設有一個包含 100 個整數的數組,每個整數占 4 字節(jié),數組在內存中是連續(xù)存儲的 。如果 Cache Line 大小為 64 字節(jié),那么一個 Cache Line 可以容納 16 個整數(64 字節(jié) / 4 字節(jié) = 16) 。當 CPU 訪問數組中的第 1 個元素時,內存會將包含第 1 個元素以及其后面 15 個元素的這 64 字節(jié)數據作為一個 Cache Line 讀取到 Cache 中 。這樣,當 CPU 接下來訪問數組中的第 2 個到第 16 個元素時,就可以直接從 Cache 中獲取,而不需要再次訪問內存,大大提高了數據訪問的效率 。

Cache Line 的大小對程序性能有著重要的影響 。如果 Cache Line 過小,雖然每次讀取的數據量少,讀取速度可能會快一些,但由于不能充分利用空間局部性,可能會導致緩存未命中次數增加;如果 Cache Line 過大,雖然能更好地利用空間局部性,減少緩存未命中次數,但每次讀取的數據量過多,可能會導致 Cache 的利用率降低,而且讀取時間也可能會變長 。因此,在設計 CPU Cache 時,需要綜合考慮各種因素,選擇合適的 Cache Line 大小,以達到最佳的性能 。

在編寫程序時,了解 Cache Line 的概念也有助于優(yōu)化程序性能 。例如,在處理數組時,可以通過合理地組織數據結構和訪問順序,使得數據訪問能夠更好地利用 Cache Line,提高緩存命中率 。比如,按行訪問二維數組通常比按列訪問更能利用 Cache Line,因為二維數組在內存中是按行存儲的,按行訪問時相鄰元素更有可能在同一個 Cache Line 中 。

三、CPU Cache的數據寫入策略

當 CPU 對 Cache 進行寫操作時,為了確保 Cache 和內存之間的數據一致性以及提高系統性能,會采用不同的數據寫入策略,其中最常見的是直寫(Write Through)和寫回(Write Back)策略 。

3.1直寫(Write Through)

直寫,也被稱為寫直通或寫穿透 。其操作邏輯非常直觀:當 CPU 要寫入數據時,數據會同時被寫入 Cache 和內存 。也就是說,只要有寫操作發(fā)生,Cache 和內存中的數據都會立即被更新 。這就好比你在兩個筆記本上同時記錄同一件事情,一個筆記本是 Cache,另一個筆記本是內存 。

直寫策略的優(yōu)點是簡單易懂,并且能夠始終保證 Cache 和內存中的數據一致性 。因為每次寫操作都同步更新了內存,所以在任何時刻,內存中的數據都是最新的 。這種一致性在一些對數據一致性要求極高的場景中非常重要,比如數據庫系統中的關鍵數據存儲 。在數據庫事務處理中,需要確保數據的持久性和一致性,直寫策略可以保證每次數據修改都能及時保存到內存中,避免了數據丟失或不一致的問題 。

然而,直寫策略也存在明顯的缺點 。由于每次寫操作都要訪問內存,而內存的訪問速度相對較慢,這就導致了寫操作的性能較低 。每次寫操作都需要等待內存寫入完成,這會增加 CPU 的等待時間,降低了系統的整體效率 。而且,頻繁地訪問內存還會增加總線的負載,因為每次寫操作都需要通過總線與內存進行數據傳輸,可能會導致總線成為系統性能的瓶頸 。例如,在一個頻繁進行數據寫入的實時監(jiān)控系統中,大量的寫操作會使 CPU 花費大量時間等待內存寫入,從而影響系統對其他任務的響應速度 。

3.2寫回(Write Back)

寫回策略的工作機制與直寫策略有很大不同 。在寫回策略中,當 CPU 進行寫操作時,數據首先被寫入 Cache,并不會立即寫入內存 。只有當被修改的 Cache Line(緩存行,是 Cache 與內存之間數據交換的最小單位)要被替換出 Cache 時(比如 Cache 已滿,需要騰出空間來存放新的數據),才會將其寫回到內存中 。

為了實現這種延遲寫入的機制,每個 Cache Line 都有一個臟標記位(Dirty Bit) 。當數據被寫入 Cache 時,臟標記位會被設置,表示該 Cache Line 中的數據已經被修改,與內存中的數據不一致 。當 Cache Line 需要被替換時,系統會檢查其臟標記位 。如果臟標記位被設置,說明該 Cache Line 中的數據是最新的,需要先將其寫回到內存中,然后再進行替換操作;如果臟標記位未被設置,說明該 Cache Line 中的數據與內存中的數據一致,直接進行替換即可 。

寫回策略的主要優(yōu)點是性能較高 。由于大部分寫操作只需要在 Cache 中進行,不需要頻繁地訪問內存,減少了內存訪問的次數,從而提高了系統的整體性能 。這種策略尤其適用于那些存在大量寫操作的應用場景,比如圖形渲染、視頻編碼等 。在圖形渲染過程中,GPU 會對大量的圖形數據進行處理和修改,采用寫回策略可以減少數據寫入內存的次數,加快渲染速度 。

與直寫策略相比,寫回策略在性能上有明顯的優(yōu)勢 。直寫策略每次寫操作都要訪問內存,而寫回策略只有在 Cache Line 被替換時才會訪問內存,大大減少了內存訪問的頻率 。但是,寫回策略的實現復雜度較高,因為它需要額外維護臟標記位,并且在 Cache Line 替換時需要進行更多的判斷和操作 。

同時,由于數據不是立即寫入內存,在系統突然斷電或崩潰的情況下,可能會導致 Cache 中已修改但未寫回內存的數據丟失,從而出現數據不一致的問題 。為了應對這種風險,一些系統會采用寫緩沖區(qū)(Write Buffer)或日志記錄(Logging)等機制來保證數據的一致性 。寫緩沖區(qū)可以在數據寫回內存之前臨時存儲數據,即使系統崩潰,也可以從寫緩沖區(qū)中恢復數據;日志記錄則可以記錄數據的修改操作,以便在系統恢復時進行數據的一致性恢復 。

四、多核時代的挑戰(zhàn):緩存一致性問題

當 CPU 對 Cache 進行寫操作時,為了確保 Cache 和內存之間的數據一致性以及提高系統性能,會采用不同的數據寫入策略,其中最常見的是直寫(Write Through)和寫回(Write Back)策略 。

4.1緩存一致性問題的產生

在多核 CPU 的時代,每個 CPU 核心都擁有自己獨立的緩存,這雖然進一步提高了數據訪問的速度,但也帶來了一個新的棘手問題 —— 緩存一致性問題 。

想象一下,有一個多核心的 CPU,其中核心 A 和核心 B 都需要訪問內存中的同一個數據 X 。一開始,數據 X 被加載到核心 A 和核心 B 各自的緩存中 。當核心 A 對緩存中的數據 X 進行修改時,此時核心 A 緩存中的數據 X 已經更新,但核心 B 緩存中的數據 X 仍然是舊的 。如果在這個時候,核心 B 讀取自己緩存中的數據 X,就會得到一個錯誤的、過時的值,這就導致了數據不一致的情況 。

以一個簡單的銀行轉賬程序為例,假設有兩個線程分別在不同的 CPU 核心上執(zhí)行轉賬操作 。這兩個線程都需要讀取賬戶余額,然后進行相應的加減操作 。如果存在緩存一致性問題,就可能出現這樣的情況:第一個線程讀取了賬戶余額為 1000 元,然后在自己的緩存中進行了減 100 元的操作,但還沒有將更新后的數據寫回內存 。

此時,第二個線程從自己的緩存中讀取賬戶余額,由于其緩存中的數據沒有更新,仍然是 1000 元,然后也進行了減 100 元的操作 。最后,兩個線程都將各自緩存中的數據寫回內存,結果賬戶余額就變成了 800 元,而實際上應該是 900 元 。這種數據不一致的情況會對程序的正確性產生嚴重影響,尤其是在涉及到金融、數據庫等對數據準確性要求極高的領域 。

4.2解決緩存一致性的方案

要解決緩存一致性問題,首先要解決的是多個 CPU 核心之間的數據傳播問題。最常見的一種解決方案呢,叫作總線嗅探。

⑴總線嗅探Bus Snooping

總線嗅探是一種解決緩存一致性問題的基本機制 。在這種機制下,每個 CPU 核心都通過總線與內存相連,并且每個核心都可以 “嗅探” 總線上的通信 。當一個 CPU 核心對自己緩存中的數據進行寫操作時,它會向總線發(fā)送一個寫請求,這個請求包含了被修改數據的地址等信息 。

總線上的其他 CPU 核心會監(jiān)聽這個請求,當它們發(fā)現請求中的地址與自己緩存中某數據的地址相同時,就會根據請求的類型(例如是寫操作還是使緩存失效的操作),對自己緩存中的數據進行相應的處理 。如果是寫操作,其他核心可能會選擇將自己緩存中的該數據標記為無效,這樣下次訪問時就會從內存中重新讀取最新的數據;如果是使緩存失效的操作,直接將對應緩存數據標記為無效即可 。

總線嗅探的優(yōu)點是實現相對簡單,不需要復雜的目錄結構來記錄緩存狀態(tài) 。它能夠快速地檢測到其他核心對共享數據的修改,從而及時更新自己的緩存,保證數據的一致性 。然而,它也存在一些明顯的缺點 。隨著 CPU 核心數量的增加,總線上的通信量會大幅增加,因為每次寫操作都要通過總線廣播通知其他核心,這會導致總線帶寬成為瓶頸,降低系統的整體性能 。而且,總線嗅探機制在處理復雜的多處理器系統時,可能會出現一些一致性問題,例如在多個核心同時進行讀寫操作時,可能會出現數據更新順序不一致的情況 。

⑵MESI 協議

MESI 協議是一種廣泛應用的緩存一致性協議,它是對總線嗅探機制的進一步優(yōu)化和完善 。MESI 代表了緩存行的四種狀態(tài):Modified(已修改)、Exclusive(獨占)、Shared(共享)和 Invalid(無效) 。

  • Modified(已修改,M):當一個 CPU 核心對緩存中的數據進行修改后,該數據所在的緩存行狀態(tài)變?yōu)?M 。此時,緩存中的數據與內存中的數據不一致,并且只有這個核心擁有該數據的最新副本 。在該緩存行被寫回內存之前,其他核心如果要訪問這個數據,必須先等待該核心將數據寫回內存 。
  • Exclusive(獨占,E):當一個 CPU 核心從內存中讀取數據到緩存時,如果其他核心的緩存中沒有該數據的副本,那么該緩存行處于 E 狀態(tài) 。此時,緩存中的數據與內存中的數據一致,并且這個核心獨占該數據 。如果有其他核心也讀取這個數據,該緩存行狀態(tài)會變?yōu)?S 。
  • Shared(共享,S):當多個 CPU 核心的緩存中都存在同一個數據的副本時,這些緩存行都處于 S 狀態(tài) 。此時,緩存中的數據與內存中的數據一致,各個核心都可以同時讀取該數據,但如果有一個核心要對數據進行寫操作,就需要先將其他核心緩存中該數據的副本失效,然后才能進行修改,修改后緩存行狀態(tài)變?yōu)?M 。
  • Invalid(無效,I):當一個緩存行中的數據被其他核心修改,或者該緩存行被替換出緩存時,它的狀態(tài)就變?yōu)?I 。處于 I 狀態(tài)的緩存行中的數據是無效的,不能被使用 。

MESI協議中的運行機制

假設有三個CPU A、B、C,對應三個緩存分別是cache a、b、 c。在主內存中定義了x的引用值為0。

圖片圖片

①單核讀取,那么執(zhí)行流程是:CPU A發(fā)出了一條指令,從主內存中讀取x。從主內存通過bus讀取到緩存中(遠端讀取Remote read),這是該Cache line修改為E狀態(tài)(獨享)。

圖片圖片

②雙核讀取,那么執(zhí)行流程是:

  • CPU A發(fā)出了一條指令,從主內存中讀取x。
  • CPU A從主內存通過bus讀取到 cache a中并將該cache line 設置為E狀態(tài)。
  • CPU B發(fā)出了一條指令,從主內存中讀取x。
  • CPU B試圖從主內存中讀取x時,CPU A檢測到了地址沖突。這時CPU A對相關數據做出響應。此時x 存儲于cache a和cache b中,x在chche a和cache b中都被設置為S狀態(tài)(共享)。

圖片圖片

③修改數據,那么執(zhí)行流程是:

  • CPU A 計算完成后發(fā)指令需要修改x.
  • CPU A 將x設置為M狀態(tài)(修改)并通知緩存了x的CPU B, CPU B將本地cache b中的x設置為I狀態(tài)(無效)
  • CPU A 對x進行賦值。

圖片圖片

④同步數據,那么執(zhí)行流程是:

  • CPU B 發(fā)出了要讀取x的指令。
  • CPU B 通知CPU A,CPU A將修改后的數據同步到主內存時cache a 修改為E(獨享)
  • CPU A同步CPU B的x,將cache a和同步后cache b中的x設置為S狀態(tài)(共享)。

圖片圖片

MESI 協議通過狀態(tài)轉換機制來保證緩存一致性 。例如,當一個處于 S 狀態(tài)的緩存行接收到一個寫請求時,擁有該緩存行的核心會向總線發(fā)送一個 Invalidate 消息,通知其他核心將它們緩存中該數據的副本失效 。其他核心收到這個消息后,將自己緩存中對應的緩存行狀態(tài)變?yōu)?I,并返回一個 Invalidate Acknowledge 消息 。當發(fā)起寫請求的核心收到所有其他核心的確認消息后,它就可以將自己緩存中的數據修改,并將緩存行狀態(tài)變?yōu)?M 。

MESI 協議的優(yōu)勢在于它能夠有效地減少總線帶寬的占用 。通過狀態(tài)轉換機制,只有在必要時才會進行總線通信,例如當一個核心要修改共享數據時才會通知其他核心使緩存失效,而不是像總線嗅探那樣每次寫操作都進行廣播 。這大大提高了系統在多核環(huán)境下的性能和可擴展性 。同時,MESI 協議還能很好地保證數據的一致性,確保各個核心在任何時刻都能訪問到正確的數據 。然而,MESI 協議的實現相對復雜,需要額外的硬件支持來維護緩存行的狀態(tài)和進行狀態(tài)轉換 。而且,在一些極端情況下,例如大量核心同時進行讀寫操作時,MESI 協議的性能也會受到一定的影響 。

五、如何利用 CPU Cache 優(yōu)化代碼

5.1數據對齊

在計算機中,數據對齊是指將數據存儲在內存地址是其自身大小整數倍的位置上 。比如,一個 4 字節(jié)的整數(如int類型),應該存儲在地址能被 4 整除的地方;一個 8 字節(jié)的雙精度浮點數(如double類型),應該存儲在地址能被 8 整除的位置 。

數據對齊對 CPU Cache 訪問效率有著重要的影響 ?,F代 CPU 在訪問內存時,是以 Cache Line 為單位進行數據讀取和寫入的,常見的 Cache Line 大小為 64 字節(jié) 。當數據對齊時,它們更有可能完整地位于同一個 Cache Line 內 。例如,有一個包含多個int類型元素的數組,每個int占 4 字節(jié) 。如果數組元素是對齊存儲的,那么連續(xù)的 16 個int元素就可以剛好存放在一個 64 字節(jié)的 Cache Line 中 。當 CPU 訪問這個數組的某個元素時,就可以一次性將包含該元素以及相鄰 15 個元素的 Cache Line 讀入 Cache,后續(xù)訪問相鄰元素時就很可能直接從 Cache 中命中,大大提高了訪問效率 。

相反,如果數據沒有對齊,就可能出現一個數據跨越兩個 Cache Line 的情況 。比如,一個int類型的數據本該存儲在地址為 4 的倍數的位置,但卻存儲在了一個非 4 倍數的地址上,這就可能導致它的一部分在一個 Cache Line 中,另一部分在另一個 Cache Line 中 。當 CPU 訪問這個數據時,就需要讀取兩個 Cache Line,增加了內存訪問次數和時間,降低了 Cache 命中率和訪問效率 。

下面通過一個簡單的 C 語言代碼示例來展示數據對齊前后的性能差異 。我們定義一個結構體,分別測試對齊和未對齊情況下對結構體數組的訪問速度 。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>

// 未對齊的結構體
struct UnalignedStruct {
    char a;
    int b;
    char c;
};

// 對齊的結構體,使用__attribute__((aligned(4)))確保結構體按4字節(jié)對齊
struct __attribute__((aligned(4))) AlignedStruct {
    char a;
    int b;
    char c;
};

// 測試函數,計算對結構體數組的訪問時間
void testAccessTime(void *arr, size_t numElements, size_t structSize) {
    clock_t start, end;
    double cpu_time_used;
    int i;

    start = clock();
    for (i = 0; i < numElements; i++) {
        // 模擬對結構體成員的訪問
        char *ptr = (char *)arr + i * structSize;
        char temp = *((char *)ptr);
        temp = *((int *)(ptr + 1));
        temp = *((char *)(ptr + 5));
    }
    end = clock();

    cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
    printf("Access time: %f seconds\n", cpu_time_used);
}

int main() {
    size_t numElements = 1000000;
    struct UnalignedStruct *unalignedArr = (struct UnalignedStruct *)malloc(numElements * sizeof(struct UnalignedStruct));
    struct AlignedStruct *alignedArr = (struct AlignedStruct *)malloc(numElements * sizeof(struct AlignedStruct));

    if (unalignedArr == NULL || alignedArr == NULL) {
        perror("malloc failed");
        return 1;
    }

    printf("Testing unaligned struct:\n");
    testAccessTime(unalignedArr, numElements, sizeof(struct UnalignedStruct));

    printf("Testing aligned struct:\n");
    testAccessTime(alignedArr, numElements, sizeof(struct AlignedStruct));

    free(unalignedArr);
    free(alignedArr);

    return 0;
}

在這個示例中,UnalignedStruct是未對齊的結構體,AlignedStruct是通過__attribute__((aligned(4)))進行了 4 字節(jié)對齊的結構體 。testAccessTime函數用于測試對結構體數組的訪問時間 。通過運行這個程序,可以發(fā)現對齊后的結構體數組訪問時間明顯比未對齊的要短,這直觀地展示了數據對齊對 CPU Cache 訪問效率和程序性能的提升作用 。在實際編程中,尤其是在對性能要求較高的場景下,合理地進行數據對齊是優(yōu)化程序性能的重要手段之一 。

5.2循環(huán)優(yōu)化

循環(huán)結構在程序中非常常見,它對 CPU Cache 命中率有著顯著的影響 。當循環(huán)中的數據訪問模式不合理時,很容易導致 Cache 未命中次數增加,從而降低程序的執(zhí)行效率 。以下是一些優(yōu)化循環(huán)以提高 CPU Cache 命中率的方法和建議 。

減少循環(huán)嵌套深度:嵌套循環(huán)會增加數據訪問的復雜性,容易導致 Cache 命中率下降 。

例如,有一個雙重嵌套循環(huán):

for (int i = 0; i < N; i++) {
    for (int j = 0; j < M; j++) {
        // 訪問數組a[i][j]
        data[i][j] = data[i][j] * 2;
    }
}

在這個例子中,如果N和M都比較大,那么在訪問data[i][j]時,由于數組元素在內存中的存儲順序是按行優(yōu)先的,當內層循環(huán)j不斷變化時,可能會頻繁地訪問不同的 Cache Line,導致 Cache 未命中次數增多 ??梢試L試將一些計算邏輯提取到外層循環(huán),減少內層循環(huán)的復雜性,從而減少 Cache 未命中 。比如,如果內層循環(huán)中有一些與j無關的計算,可以將其移到外層循環(huán):

// 假設存在一些與j無關的計算,這里簡化為一個常量計算
int constant = someComplexCalculation();
for (int i = 0; i < N; i++) {
    // 與j無關的計算結果在每次i循環(huán)中可以復用
    int temp = someCalculationBasedOnI(i, constant);
    for (int j = 0; j < M; j++) {
        // 利用與j無關的計算結果進行內層循環(huán)操作
        data[i][j] = temp * data[i][j];
    }
}

合理安排循環(huán)順序:根據數據在內存中的存儲方式和訪問的局部性原理,合理安排循環(huán)順序可以提高 Cache 命中率 。以二維數組為例,二維數組在內存中是按行存儲的 。

如果按行優(yōu)先的順序訪問二維數組,更能利用空間局部性原理 。比如:

// 按行優(yōu)先訪問
for (int i = 0; i < ROWS; i++) {
    for (int j = 0; j < COLS; j++) {
        sum += matrix[i][j];
    }
}

這種訪問方式下,當訪問matrix[i][j]時,由于空間局部性,matrix[i][j + 1]很可能也在同一個 Cache Line 中,從而提高了 Cache 命中率 。而如果按列優(yōu)先訪問:

// 按列優(yōu)先訪問
for (int j = 0; j < COLS; j++) {
    for (int i = 0; i < ROWS; i++) {
        sum += matrix[i][j];
    }
}

在這種情況下,每次訪問matrix[i][j]時,下一個訪問的matrix[i + 1][j]很可能在不同的 Cache Line 中,導致Cache未命中次數增加,降低了訪問效率 。所以,在大多數情況下,按行優(yōu)先訪問二維數組能更好地利用 CPU Cache,提高程序性能 。

在編寫程序時,充分考慮循環(huán)結構對 CPU Cache 命中率的影響,并運用上述優(yōu)化方法,可以顯著提高程序的運行效率 。尤其是在處理大規(guī)模數據和對性能要求苛刻的應用場景中,循環(huán)優(yōu)化是提升程序性能的關鍵環(huán)節(jié)之一 。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2019-04-08 10:09:04

CPU緩存高性能

2019-12-10 08:10:35

LinuxCPU性能優(yōu)化

2019-07-26 06:30:37

CPU代碼操作系統

2023-02-02 08:04:15

Ceph數據CPU

2020-09-23 09:21:56

CPUCache緩存

2021-02-02 13:45:31

Vue代碼前端

2012-10-09 09:43:50

WLAN優(yōu)化無線局域網WLAN

2025-02-25 12:00:00

Java線程開發(fā)

2019-03-14 15:38:19

ReactJavascript前端

2012-07-23 10:22:15

Python性能優(yōu)化優(yōu)化技巧

2009-05-08 09:01:03

微軟Windows 7操作系統

2011-10-19 09:41:15

ASP.NET性能優(yōu)化

2019-03-22 09:50:52

WebJavaScript前端

2020-06-11 13:03:04

性能優(yōu)化緩存

2011-06-14 11:14:10

性能優(yōu)化代碼

2009-04-16 17:24:54

性能優(yōu)化SQL Server 數據收集

2015-12-11 11:49:19

java

2015-12-11 11:39:15

.net代碼

2023-11-01 11:51:08

Linux性能優(yōu)化

2010-03-11 16:49:55

Linux CPU利用
點贊
收藏

51CTO技術棧公眾號