CPU高速缓存行对齐和代码优化
CacheLine
眾所周知,計算機(jī)將數(shù)據(jù)從主存讀入Cache時,是把要讀取數(shù)據(jù)附近的一部分?jǐn)?shù)據(jù)都讀取進(jìn)來這樣一次讀取的一組數(shù)據(jù)就叫做CacheLine,每一級緩存中都能放很多的CacheLine
兩種方法查看:
1.cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
2.?cat /proc/cpuinfo
3.lscpu
多核CUP
L1、L2、L3指一級緩存,二級緩存,三級緩存。其中一級緩存分指令緩存和數(shù)據(jù)緩存,通過lscpu命令,可以看到l1d和l1i。
CUP中的每個核均可單獨(dú)處理一個線程
每個核公用L3
超線程
一個核中有多套PC和Register,他們公用一個ALU,這樣一個核可以處理多個線程
如四核八線程就由此而來
Volatile的可見性
1、x被標(biāo)記了volatile
2、兩個線程運(yùn)算時是將緩存中要被運(yùn)算數(shù)所在的整條CacheLine復(fù)制到線程自己的存儲,并進(jìn)行運(yùn)算,運(yùn)算之后寫回緩存
3、假設(shè)線程1修改了x并寫回,但是線程2中的x還是未修改的x
4、由于x被標(biāo)記了volatile,在線程1寫回x緩存時,線程1會通知線程2重新讀取緩存中的x
偽共享
1、線程1、2公共使用同一個CacheLine
2、x、y在同一個CacheLine
3、x、y都是volatile(x和y不是線程安全的,如果不是volatile,數(shù)據(jù)會不同步)
4、如果線程1不斷修改x,線程2不斷修改y,那么修改的時候線程1就要不斷通知線程2更新x、線程2就要不斷通知線程1更新y
5、這樣的不斷通知不斷重新讀取很浪費(fèi)性能
6、這就叫偽共享
CacheLine對齊
多線程會有上面的偽共享的問題,如果在緩存讀取數(shù)據(jù)到CacheLine時,兩個volatile的數(shù)被讀取到不同的CacheLine中的話,就不需要一直通知另一個線程更新數(shù)據(jù)了,因為另一個線程根本沒有這個數(shù)據(jù)
那么如何讓兩個數(shù)據(jù)一定在不同的CacheLine呢,方法就是Cache Line對齊
一般一個CacheLine是64字節(jié),也就是8個long,我們可以把x定義為long,并同時定義7個沒有用的long變量,這樣這8個數(shù)就在同一個CacheLine中
之后再定義y,y自然也就在下一個CacheLine中了
這就叫CacheLine對齊,這樣兩線程就不會出現(xiàn)偽共享的現(xiàn)象了
? ? ? ?CPU的高速緩存一般分為一級緩存、二級緩存和三級緩存。CPU在運(yùn)行時首先從一級緩存讀取數(shù)據(jù),如果讀取失敗則會從二級緩存讀取數(shù)據(jù),如果仍然失敗則再從三級緩存,再失敗最后從內(nèi)存中存讀取數(shù)據(jù)。而CPU從緩存或主內(nèi)存中最終讀取到數(shù)據(jù)所耗費(fèi)的時鐘周期差距是非常之大的。因此高速緩存的容量和速度直接影響到CPU的工作性能。一級緩存都內(nèi)置在CPU內(nèi)部并與CPU同速運(yùn)行,可以有效的提高CPU的運(yùn)行效率。一級緩存越大,CPU的運(yùn)行效率往往越高。
緩存對齊如何編碼
?一級緩存又分為數(shù)據(jù)緩存和指令緩存,他們都由高速緩存行組成,對于X86_64架構(gòu)的CPU來說,高速緩存行一般是64個字節(jié),CPU大約只有512行高速緩存行,也就是說約32k的一級緩存。二級緩存一般有1-2MB,三級緩存可以達(dá)到33MB-64MB.
?查看cpu0 的一級緩存中的有多少組
$ cat /sys/devices/system/cpu/cpu0/cache/index0/number_of_sets
$64
查看cpu0的一級緩存中一組中的行數(shù)
$cat /sys/devices/system/cpu/cpu0/cache/index0/ways_of_associativity
$8
? ? ? ? 當(dāng)CPU需要讀取一個變量時,該變量所在的以64字節(jié)分組的內(nèi)存數(shù)據(jù)將被一同讀入高速緩存行,所以,對于性能要求嚴(yán)格的程序來說,充分利用高速緩存行的優(yōu)勢非常重要。一次性將訪問頻繁的64字節(jié)數(shù)據(jù)對齊后讀入高速緩存中,減少CPU高級緩存與低級緩存、內(nèi)存的數(shù)據(jù)交換。
? ? ? ?但是對于多CPU的計算機(jī),情況卻又不一樣了。例如:
1、??????CPU1?讀取了一個字節(jié),和它相鄰的字節(jié)被讀入?CPU1?的高速緩存。
2、??????CPU2?做了上面同樣的工作。這樣?CPU1?,?CPU2?的高速緩存擁有同樣的數(shù)據(jù)。
3、??????CPU1?修改了那個字節(jié),被修改后,那個字節(jié)被放回?CPU1?的高速緩存行。但是該信息并沒有被寫入RAM?。
4、??????CPU2?訪問該字節(jié),但由于?CPU1?并未將數(shù)據(jù)寫入?RAM?,導(dǎo)致了數(shù)據(jù)不同步。
? ? ? ? 當(dāng)一個?CPU?修改高速緩存行中的字節(jié)時,計算機(jī)中的其它?CPU會被通知,它們的高速緩存將視為無效。于是,在上面的情況下,?CPU2?發(fā)現(xiàn)自己的高速緩存中數(shù)據(jù)已無效,?CPU1?將立即把自己的數(shù)據(jù)寫回?RAM?,然后?CPU2?重新讀取該數(shù)據(jù)。?可以看出,高速緩存行在多處理器上會導(dǎo)致一些不利。
? ? ? ? ?從上面的情況可以看出,在設(shè)計數(shù)據(jù)結(jié)構(gòu)的時候,應(yīng)該盡量將只讀數(shù)據(jù)與讀寫數(shù)據(jù)分開,并具盡量將同一時間訪問的數(shù)據(jù)組合在一起。這樣?CPU?能一次將需要的數(shù)據(jù)讀入。
如:
struct __a {int id; // 不易變int factor;// 易變char name[64];// 不易變int value;// 易變 };這樣的數(shù)據(jù)結(jié)構(gòu)就很不利。
在?X86_64?下,可以試著修改和調(diào)整它
struct __a {char name[64];// 不易變int id; // 不易變char __Align[64 – sizeof(int)+sizeof(name)*sizeof(name[0])%64]int factor;// 易變int value;// 易變char __Align2[64 – 2* sizeof(int)%64] };64 – sizeof(int)+sizeof(name)*sizeof(name[0])%64
64表示?X86_64?架構(gòu)緩存中,高速緩存行為64字節(jié)?大小。?__Align?用于顯式對齊。
再來一個有利于高速緩存行的例子:
struct CUSTINFO {DWORD dwCustomerID; //Mostly read-onlyint nBalanceDue; //Read-writechar szName[100]; //Mostly read-onlyFILETIME ftLastOrderDate; //Read-write };改版后的結(jié)構(gòu)定義?:
// Determine the cache line size for the host CPU. //為各種CPU定義告訴緩存行大小 #ifdef _X86_ #define CACHE_ALIGN 32 #endif#ifdef _X86_64 #define CACHE_ALIGN 64 #endif#ifdef _ALPHA_ #define CACHE_ALIGN 64 #endif#ifdef _IA64_ #define CACHE_ALIGN ?? #endif#define CACHE_PAD(Name, BytesSoFar) \ BYTE Name[CACHE_ALIGN - ((BytesSoFar) % CACHE_ALIGN)]struct CUSTINFO {DWORD dwCustomerID; // Mostly read-onlychar szName[100]; // Mostly read-only//Force the following members to be in a different cache line.//這句很關(guān)鍵用一個算出來的Byte來填充空閑的告訴緩存行//如果指定了告訴緩存行的大小可以簡寫成這樣//假設(shè)sizeof(DWORD) + 100 = 108;告訴緩存行大小為32//BYTE[12];//作用呢就是強(qiáng)制下面的數(shù)據(jù)內(nèi)容與上面數(shù)據(jù)內(nèi)容不在同一高速緩存行中。CACHE_PAD(bPad1, sizeof(DWORD) + 100);int nBalanceDue; // Read-writeFILETIME ftLastOrderDate; // Read-write//Force the following structure to be in a different cache line.CACHE_PAD(bPad2, sizeof(int) + sizeof(FILETIME)); };??? 高速緩存控制器是針對數(shù)據(jù)塊,而不是字節(jié)進(jìn)行操作的。從程序設(shè)計的角度講,高速緩存其實就是一組稱之為緩存行(cache line)的固定大小的數(shù)據(jù)塊,其大小是以突發(fā)讀或者突發(fā)寫周期的大小為基礎(chǔ)的。
? ? 每個高速緩存行完全是在一個突發(fā)讀操作周期中進(jìn)行填充或者下載的。即使處理器只存取一個字節(jié)的存儲器,高速緩存控制器也啟動整個存取器訪問周期并請求整個數(shù)據(jù)塊。緩存行第一個字節(jié)的地址總是突發(fā)周期尺寸的倍數(shù)。緩存行的起始位置總是與突發(fā)周期的開頭保持一致。
? ? 現(xiàn)代處理器有專門的功能單元來執(zhí)行加載和存儲操作。加載單元每個時鐘周期只有啟動一條加載操作;與加載操作一樣,在大多數(shù)情況下,存儲操作能夠在完整流水線化的模式中工作,每個周期開始一條新的存儲。
關(guān)于內(nèi)存字節(jié)對齊
https://blog.csdn.net/yizhiniu_xuyw/article/details/109622878
? ? ? ?
?
總結(jié)
以上是生活随笔為你收集整理的CPU高速缓存行对齐和代码优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。