false-sharing原理浅析和测试
緒論
SMP(對(duì)稱多處理)架構(gòu)簡(jiǎn)單的說就是多個(gè)CPU核,共享同一個(gè)內(nèi)存和總線。L1 cache也叫芯片緩存,一般是CPU Core私有的,即每個(gè)CPU核一個(gè),L2 cache可能是私有的也可能是部分共享的,L3 cache則多數(shù)是共享的。false-sharing是在SMP的架構(gòu)下常見的問題。?
false-sharing產(chǎn)生背景及原因
CPU利用cache和內(nèi)存之間交換數(shù)據(jù)的最小粒度不是字節(jié),而是稱為cache line的一塊固定大小的區(qū)域,緩存行是內(nèi)存交換的實(shí)際單位。緩存行是2的整數(shù)冪個(gè)連續(xù)字節(jié),一般為32-256個(gè)字節(jié),最常見的緩存行大小是64個(gè)字節(jié)。
在寫多線程代碼時(shí),為了避免使用鎖,通常會(huì)采用這樣的數(shù)據(jù)結(jié)構(gòu):根據(jù)線程的數(shù)目,安排一個(gè)數(shù)組, 每個(gè)線程一個(gè)項(xiàng),互相不沖突。從邏輯上看這樣的設(shè)計(jì)無懈可擊,但是實(shí)踐的過程可能會(huì)發(fā)現(xiàn)有些場(chǎng)景下非但沒提高執(zhí)行速度,反而會(huì)性能很差,而且年輕司機(jī)通常很難定位問題所在。
問題在于cpu的cache line,當(dāng)多線程修改互相獨(dú)立的變量時(shí),如果這些變量共享同一個(gè)緩存行,就會(huì)無意中影響彼此的性能,這就是偽共享,即false-sharing。
實(shí)際案例
在多處理器,多線程情況下,如果兩個(gè)線程分別運(yùn)行在不同的CPU上,而其中某個(gè)線程修改了cache line中的元素,由于cache一致性的原因,另一個(gè)線程的cache line被宣告無效,在下一次訪問時(shí)會(huì)出現(xiàn)一次cache line miss,大量的cache line miss會(huì)導(dǎo)致性能的顯著下降。究其原因,cache line miss是由于兩個(gè)線程的Cache line有重合(非共享的變量實(shí)際上卻共享的使用了同一個(gè)cacheline,導(dǎo)致競(jìng)爭(zhēng))引起的。
如在Intel Core 2 Duo處理器平臺(tái)上,L2 cache是由兩個(gè)core共享的,而L1 data cache是分開的,由兩個(gè)core分別存取。cache line的大小是64 Bytes。假設(shè)有個(gè)全局共享結(jié)構(gòu)體變量f由2個(gè)線程A和B共享讀寫,該結(jié)構(gòu)體一共8個(gè)字節(jié)同時(shí)位于同一條cache line上。
struct foo {int x;int y; };若此時(shí)兩個(gè)線程一個(gè)讀取f.x另一個(gè)讀取f.y,即便兩個(gè)線程的執(zhí)行是在獨(dú)立的cpu core上的,實(shí)際上結(jié)構(gòu)體對(duì)象f被分別讀入到兩個(gè)CPUs的cache line中且該cache line 處于shared狀態(tài)。此時(shí)在核心1上運(yùn)行的線程A想更新變量X,同時(shí)核心2上的線程B想要更新變量Y。
如果核心1上線程A優(yōu)先獲得了所有權(quán),線程A修改f.x會(huì)使該CPU core 1 上的這條cache line將變?yōu)閙odified狀態(tài),另一個(gè)CPU core 2上對(duì)應(yīng)的cache line將變成invalid狀態(tài);此時(shí)若線程B馬上讀取f.y,為了確保cache一致性,B所在CPU核上的相應(yīng)cache line的數(shù)據(jù)必須被更新;當(dāng)核心2上線程B優(yōu)先獲得了所有權(quán)然后執(zhí)行更新操作,核心1就要使自己對(duì)應(yīng)的緩存行失效。這會(huì)來來回回的經(jīng)過L3緩存,大大影響了性能。如果互相競(jìng)爭(zhēng)的核心位于不同的插槽,就要額外橫跨插槽連接,若讀寫的次數(shù)頻繁,將增大cache miss的次數(shù),嚴(yán)重影響系統(tǒng)性能。
雖然在memory的角度這兩種的訪問時(shí)隔離的,但是由于錯(cuò)誤的緊湊地放在了一起,是的兩個(gè)變量處于同一個(gè)緩存行中。每個(gè)線程都要去競(jìng)爭(zhēng)緩存行的所有權(quán)來更新變量。可見,false sharing會(huì)導(dǎo)致多核處理器上對(duì)于緩存行cache line的寫競(jìng)爭(zhēng),造成嚴(yán)重的系統(tǒng)性能下降,有人將偽共享描述成無聲的性能殺手,因?yàn)閺拇a中很難看清楚是否會(huì)出現(xiàn)偽共享。
false-sharing避免方法
把每個(gè)項(xiàng)湊齊cache line的長(zhǎng)度,即可實(shí)現(xiàn)隔離,雖然這不可避免的會(huì)浪費(fèi)一些內(nèi)存。
注意事項(xiàng)
單線程或單核多線程都不存在這個(gè)問題,因?yàn)橹挥幸粋€(gè)CPU核也即只有一個(gè)L1 Cache,不存在緩存一致性的問題。
示例程序
注意程序中的LEVEL1_DCACHE_LINESIZE宏來自g++編譯命令傳入的,使用Shell命令getconf LEVEL1_DCACHE_LINESIZE能獲取cpu cache line的大小。(有關(guān)getconf命令的使用可以自行g(shù)oogle)
#include <stdio.h> #include <sys/time.h> #include <time.h> #include <pthread.h> #define PACK __attribute__ ((packed)) typedef int cache_line_int __attribute__((aligned(LEVEL1_DCACHE_LINESIZE)));#ifdef FS struct data {cache_line_int a;cache_line_int b; }; #endif #ifdef NONFS struct data {int a;int b; }; #endif#define MAX_NUM 500000000void* thread_func_1(void* param) {timeval start, end;gettimeofday(&start, NULL);data* d = (data*)param;for (int i=0; i<MAX_NUM; ++i){++d->a;}gettimeofday(&end, NULL);printf("thread 1, time=%d\n", (int)(end.tv_sec-start.tv_sec)*1000000+(int)(end.tv_usec-start.tv_usec));return NULL; }void* thread_func_2(void* param) {timeval start, end;gettimeofday(&start, NULL);data* d = (data*)param;for (int i=0; i<MAX_NUM; ++i){++d->b;}gettimeofday(&end, NULL);printf("thread 2, time=%d\n", (int)(end.tv_sec-start.tv_sec)*1000000+(int)(end.tv_usec-start.tv_usec));return NULL; }int main() {data d = {a:0, b:0};printf("sizeof(data) : %d\n", sizeof(data));pthread_t t1, t2;pthread_create(&t1, NULL, thread_func_1, &d);pthread_create(&t2, NULL, thread_func_2, &d);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("end, a=%d,b=%d\n", d.a, d.b);return 0; }編譯、運(yùn)行可以看到結(jié)果對(duì)比:
/*編譯指令*/ g++ -o 1 1.cpp -g -Wall -lpthread -DLEVEL1_DCACHE_LINESIZE=`getconf LEVEL1_DCACHE_LINESIZE` -DFS g++ -o 1 1.cpp -g -Wall -lpthread -DLEVEL1_DCACHE_LINESIZE=`getconf LEVEL1_DCACHE_LINESIZE` -DNONFS/*輸出結(jié)果:*/ thread 1, time=1607430 thread 2, time=1629508我的騰訊云主機(jī)只有一個(gè)CPU核,所以運(yùn)行的結(jié)果并沒有差異,但是在多核CPU上執(zhí)行大約相差2~3倍。
?
注:本文整理自多篇文章,參考文章列表后續(xù)補(bǔ)充。
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/blastbao/p/8290332.html
總結(jié)
以上是生活随笔為你收集整理的false-sharing原理浅析和测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第二节 -- python的基础语法
- 下一篇: orb slam2