C1-局部性原理-M

CPU的局部性原理非常大的影響到了程序的性能髓需,很多性能調(diào)優(yōu)的場景就跟這個相關(guān)。比如說:Cacheline房蝉,字節(jié)對齊僚匆,甚至這個原理影響了編程語言的設(shè)計。程序員需要掌握這個知識來理解編程語言與CPU的結(jié)構(gòu)搭幻。

介紹一些事實

  • 用數(shù)組在任何情況下的效率都要比用鏈表高咧擂;
  • 程序中應(yīng)該多用循環(huán)少用遞歸
  • 二維數(shù)組必須先循環(huán)行,再循環(huán)列檀蹋;
  • Cacheline就是根據(jù)這個原理設(shè)計的松申,理解了局部性原理可以理解為啥有“偽共享”以及怎么讓變量常駐一個Cacheline中。

原理簡單但是推論多

局部性原理分類

  • 時間局部性:n時刻cpu訪問過的代碼與數(shù)據(jù)在n+1的時刻還會被訪問
  • 空間局部性:m位置cpu訪問過的代碼與數(shù)據(jù),則[m,m+n]位置的數(shù)據(jù)也會馬上被訪問到贸桶,簡單講就是一旦訪問了一個地址舅逸,那么鄰居都要準備好被訪問餐抢。

舉個例子:

int i;
int array[N] = {0}
for(i=0;i<N;j++){
array[i] =1;
}

先看看空間局部性

  1. 數(shù)組可以說是編程語言里面比較常用的結(jié)構(gòu)了(hash table就是用數(shù)組特性實現(xiàn)的匙头,你想想用得多不多)。根據(jù)空間局部性原理甸祭,當CPU訪問到i=0的元素時设联,i+n個字節(jié)其實已經(jīng)從內(nèi)存被加載到CPU緩存了(CPU訪問內(nèi)存的金字塔結(jié)構(gòu)這里默認大家都知道了善已,不知道看這里 - Numbers Everyone Should Know小節(jié))。接下來訪問i=1,i=2,i=3....i=n都不需要再去訪問內(nèi)存了离例,因為都被CPU cache了换团,這就是空間局部性原理的使用。

  2. 接著上面的宫蛆,n就是CPU一次cache多大的范圍合適呢艘包?現(xiàn)在x64CPU就是64個字節(jié),這個cache也有個專屬名字——cacheline耀盗。至于為啥是64個字節(jié)想虎,應(yīng)該是個經(jīng)驗值,這個大小對緩存程序叛拷,數(shù)據(jù)的粒度剛剛好吧舌厨,不糾結(jié)。還有很多數(shù)據(jù)都是經(jīng)驗數(shù)據(jù)忿薇,比如OS的頁大小4k等等裙椭。

  3. 接著看鏈表,鏈表這種數(shù)據(jù)結(jié)構(gòu)的優(yōu)點是靈活署浩,可以用O(1)做插入與刪除揉燃,也就是擴容性能不錯,可以應(yīng)對未知大小的數(shù)據(jù)需求筋栋,但是根據(jù)空間局部性原理炊汤,隨機訪問性能就被拉低了,看看圖:

    數(shù)組VS鏈表

    套用分布式緩存的概念弊攘,鏈表這種數(shù)據(jù)結(jié)構(gòu)更容易導(dǎo)致CPU的緩存被擊穿抢腐。所以C++語言的作者都推薦大家在任何情況下都不要使用鏈表。(出處已經(jīng)忘記了……)肴颊。那么怎么利用數(shù)組做出可以靈活添加刪除元素的結(jié)構(gòu)呢氓栈?答案是——ringbuffer
    然而鏈表并沒有消失婿着,因為數(shù)組的原罪動態(tài)擴容沒有解決授瘦,所以導(dǎo)致兩個后果
    3.1. Hashtable在任何語言實現(xiàn)下對于擴容的性能都非常不友好醋界,特別是在多核CPU多線程并發(fā)情況下尤其消耗性能,導(dǎo)致在很多讀寫相當提完,性能critcal的場景下hashtable不是一個理想的選擇形纺。因為會抖動,所以不是一種穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)徒欣。例子:linux中很少用hashtable對未知大小的數(shù)據(jù)做檢索逐样,轉(zhuǎn)而使用紅黑樹;redis用跳表zset的實現(xiàn)打肝。這都用了鏈表的特性脂新。
    3.2. 啟示——在工程上有些瓶頸是不要試圖去突破的,比如鏈表與數(shù)組粗梭,你不要期望可以設(shè)計出一種完美的數(shù)據(jù)結(jié)構(gòu)來解決所有的問題争便,永遠都是去做權(quán)衡,而不是盲目自信去做無謂的浪費断医。有很多例子都是經(jīng)過數(shù)學證明的滞乙,不要試圖去突破,比如CAP鉴嗤,比如排序算法的效率斩启。

  4. 一個需要優(yōu)化的代碼例子(當然這里的n與m要足夠大,越大越明顯):

int a[n][m];
for(int i=0; i<m;i++){
  for(int j=0;j<n;j++){
    a[j][i] =1;
  }
}

這個例子的意思是先訪問列再訪問行醉锅,訪問的步長是n個元素兔簇,如果n等于16,就正好跨過了一個緩存行硬耍,正好j每++一次就導(dǎo)致一次CPU緩存被擊穿男韧,性能就巨差。正確的代碼:

int a[n][m];
for(int i=0; i<n;i++){
  for(int j=0;j<m;j++){
    a[i][j] =1;
  }
}
二維數(shù)組內(nèi)存布局

所以默垄,在寫代碼時,要多想想CPU甚纲,多想想操作系統(tǒng)才能寫出性能優(yōu)秀的代碼口锭。

時間局部性

  1. 時間局部性簡單說就是訪問過的代碼、數(shù)據(jù)還會馬上被訪問到介杆。為什么有這個結(jié)論呢鹃操?你如果感興趣的話,可以看看你家的代碼庫中春哨,或者開源軟件的源代碼中荆隘,是不是循環(huán)語句最多?或者不較真的說赴背,循環(huán)代碼最重要椰拒。為什么呢晶渠?
    1.1. 因為算法都是要被寫成循環(huán)或者遞歸形式的,不能用循環(huán)或者遞歸實現(xiàn)的算法不存在燃观;
    1.2. 這叫做程序的可停止性褒脯,這是圖靈的主要功績,圖靈證明了任何計算都是可以用圖靈機在有限步里面完成的缆毁。
  2. 循環(huán)代碼就是時間局部性原理的基礎(chǔ)番川。因為一個循環(huán)如果被CPU cache住了,就不用去內(nèi)存加載了脊框,從而提高了程序的運行效率颁督。
    3、啟示——性能好的程序往往會寫成循環(huán)的代碼而不是遞歸浇雹。遞歸函數(shù)調(diào)用是高級語言的特性沉御,但是它涉及到代碼從一個點反復(fù)的跳轉(zhuǎn)到不同的代碼段,會導(dǎo)致cpu cache bouncing箫爷,程序性能會波動嚷节;
    循環(huán)vs遞歸
  3. 啟示2——人越容易看懂的代碼對機器也越不友好。在算法設(shè)計中虎锚,遞歸法是人能看懂的最好編碼的形式硫痰,但是實際運行的效率不高;相反如果寫成循環(huán)形式窜护,人雖然理解起來有困難效斑,但是機器十分友好,如:
void preOrder1(BinTree *root)     //遞歸前序遍歷 
{
    if(root!=NULL)
    {
        cout<<root->data<<"";
        preOrder1(root->lchild);
        preOrder1(root->rchild);
    }
}
void preOrder2(BinTree *root)     //非遞歸前序遍歷 
{
    stack<BinTree*> s;
    BinTree *p=root;
    while(p!=NULL||!s.empty())
    {
        while(p!=NULL)
        {
            cout<<p->data<<"";
            s.push(p);
            p=p->lchild;
        }
        if(!s.empty())
        {
            p=s.top();
            s.pop();
            p=p->rchild;
        }
    }
}

所以還是那樣柱徙,自然界的這種蹺蹺板很多缓屠,一個東西有好的方面也肯定有其不足之處,這是很正常的护侮。

認識到事物的邊界才算是真正認識到了它敌完。

總結(jié)

計算機硬件與軟件的性能都會受到局部性原理的限制,而局部性原理又分為空間時間局部性兩個維度羊初,正是這兩個約束(枷鎖)使得我們的計算機世界長成了現(xiàn)在這個樣子滨溉;而程序員懂得其中的基本概念,在寫代碼的時候能夠有點印象就已經(jīng)可以了长赞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末晦攒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子得哆,更是在濱河造成了極大的恐慌脯颜,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贩据,死亡現(xiàn)場離奇詭異栋操,居然都是意外死亡闸餐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門讼庇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绎巨,“玉大人,你說我怎么就攤上這事蠕啄〕∏冢” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵歼跟,是天一觀的道長和媳。 經(jīng)常有香客問我,道長哈街,這世上最難降的妖魔是什么留瞳? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮骚秦,結(jié)果婚禮上她倘,老公的妹妹穿的比我還像新娘。我一直安慰自己作箍,他們只是感情好硬梁,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胞得,像睡著了一般荧止。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阶剑,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天跃巡,我揣著相機與錄音,去河邊找鬼牧愁。 笑死素邪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的猪半。 我是一名探鬼主播娘香,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼办龄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淋昭,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤俐填,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后翔忽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體英融,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡盏檐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了驶悟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胡野。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖痕鳍,靈堂內(nèi)的尸體忽然破棺而出硫豆,到底是詐尸還是另有隱情,我是刑警寧澤笼呆,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布熊响,位于F島的核電站,受9級特大地震影響诗赌,放射性物質(zhì)發(fā)生泄漏汗茄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一铭若、第九天 我趴在偏房一處隱蔽的房頂上張望洪碳。 院中可真熱鬧,春花似錦叼屠、人聲如沸瞳腌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纯趋。三九已至,卻和暖如春冷离,著一層夾襖步出監(jiān)牢的瞬間吵冒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工西剥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留痹栖,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓瞭空,卻偏偏與公主長得像揪阿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咆畏,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容