讀書筆記|數(shù)據(jù)局部性

前言


CPU的速度飛速增長,然而計算機對RAM訪問速度的增長卻很遲緩憔足。CPU速度增長帶來的優(yōu)勢有時并不能為我們的程序帶來同樣的速度提升。

處理器的速度提升,使得我們可以更快的處理數(shù)據(jù)垦搬,但是卻不能讓計算機更快的獲取數(shù)據(jù)。為了讓CPU進行運算艳汽,它需要從主存中取出數(shù)據(jù)猴贰,并放到寄存器中,但是RAM的存取速度遠遠慢于CPU的速度河狐。這就導致CPU可能會花費驚人的時間來等待內存?zhèn)鬏敂?shù)據(jù)米绕。

一個形象的比喻


書中的比喻非常棒瑟捣,想了想,還是大概抄下來把栅干。

設想你是一個小辦公室里的會計迈套。你的工作是采集一盒子的單子并對它們進行一些核查統(tǒng)計或其他的計算。
得益于努力工作碱鳞、出色的才能以及進取心桑李,你可以在(比如說)一分鐘內完成一個盒子里的所有任務。當然劫笙,這里有個小問題芙扎。這些盒子都被分別存放于一棟樓里的某個地方。為了拿到這些盒子填大,你必須詢問倉儲人員來獲取這些盒子戒洼。他去開來叉車并在過道之間尋找直至找到你想要的那個盒子。
他花了一整天的時間來取一個盒子允华。這意味著不論你辦事效率有多高圈浇,你一天只能搞定一個盒子。而剩余的時間靴寂,你就只能坐在辦公椅上思考自己怎么就干了這樣一份傷神的工作磷蜀。
某天,一群工業(yè)設計師出現(xiàn)了百炬。他們的任務是提高工作效率褐隆,比如提高流水線效率之類的工作。在觀察你工作了幾天之后剖踊,他們注意到以下幾點:

  • 通常情況下庶弃,在你完成某個盒子里的任務之后,你所需要的下個盒子就放在倉儲間中與這個盒子所在的同個架子上德澈。
  • 開著叉車來取個小盒子真是蠢哭了歇攻。
  • 其實在你的辦公室角落里有一些空閑的空間。
    它們想到了一個聰明的辦法梆造。倉庫管理員為你帶來你要的盒子時缴守,會將與它相鄰的那些盒子也都一起帶來。現(xiàn)在當你需要一個新盒子時镇辉,第一件要做的事情就是查看盒子是否在辦公室的托盤上屡穗。如果在,那就太棒了忽肛!你只需要幾秒的時間把它拿過來然后繼續(xù)你的算數(shù)鸡捐。

CPU的托盤


上面的過程與當今計算機CPU工作原理相似。你相當于CPU麻裁,你的桌面是寄存器箍镜,裝單子的盒子是數(shù)據(jù)源祈,倉庫是機器的RAM,倉庫管理員是讀取數(shù)據(jù)的總線色迂。

CPU內部的內存十分有限香缺,這一小塊內存被成為緩存,這相當于那個裝滿盒子的托盤歇僧。當CPU需要RAM中的數(shù)據(jù)時图张,他它會自動將一整塊的連續(xù)的內存取出來并放到緩存中。

加入你需要使用的下一個數(shù)據(jù)就恰好在緩存中诈悍,那么CPU就能直接從緩存中獲取數(shù)據(jù)祸轮,這是很快的。成功在緩存中找到數(shù)據(jù)被稱為一次命中侥钳,反之則稱為未命中适袜。

當緩存未命中時,CPU就停止運轉舷夺,直到取得數(shù)據(jù)苦酱,我們的任務就是盡量避免這個情況的發(fā)生。

代碼的好壞當然會影響性能给猾,但是疫萤,數(shù)據(jù)的影響同樣非常大,你需要做的就是讓緩存中可用的數(shù)據(jù)盡可能的多敢伸。在緩存中使用的數(shù)據(jù)越多扯饶,程序就跑得越快。

所以優(yōu)化就變成了如何讓要處理的數(shù)據(jù)在內存中兩兩相鄰池颈。如下圖所示尾序,如果你要按順序處理A、B饶辙、C三件事蹲诀,那么斑粱,A弃揽、B、C最好在內存中是這樣布局的:


優(yōu)化


去使用上述優(yōu)化最重要的是要找到需要優(yōu)化的地方则北。并不是所有代碼都需要進行這樣的優(yōu)化矿微,對那些并不會頻繁執(zhí)行的代碼進行優(yōu)化將會浪費你大量的時間,并使你的代碼更加復雜難看尚揣。

當然涌矢,你還要確定程序的性能問題是否是由緩存未命中引起的,如果不是快骗,還是暫時不要在這上面浪費時間了娜庇。

另外塔次,需要注意的是,使用抽象化的結構名秀、使用借口意味著要通過指針或引用來訪問對象励负,這會導致訪問的內存不是連續(xù)的,這就會導致緩存未命中的現(xiàn)象匕得,是否要犧牲一些抽象化的工作來提高效率继榆,需要進行權衡。

示例


在游戲中汁掠,使用組建模式略吨,將不同的模塊拆分:AI、物理考阱、渲染等翠忠。以下是GameEntity類與其他組件類:

class GameEntity {
public:
    GameEntity(AIComponent* ai, PhysicsComponent* physics, RenderComponent* render): 
      ai_(ai), physics_(physics), render_(render) {}

    AIComponent* ai() { return ai_;}
    PhysicsComponent* physics() { return physics_;}
    RenderComponent* render() { return render_;}

private:
    AIComponent* ai_;
    PhysicsComponent* physics_;
    RenderComponent* return render_;
};

class AIComponent {
public:
    void update() {
        //do something
    }
//other members
};

class PhysicsComponent {
public:
    void update() {
        //do something
    }
//other members
};

class RenderComponent {
public:
    void update() {
        //do something
    }
//other members
};

當游戲更新時,我們要分別調用各組建的update函數(shù)進行更新:

while(!gameover){
    for (int i = 0;i < num; ++ i) {
        entities[i]->ai()->update();
    }
    for (int i = 0;i < num; ++ i) {
        entities[i]->physics()->update();
    }
    for (int i = 0;i < num; ++ i) {
        entities[i]->render()->update();
    }
}

上面的代碼不僅引起緩存抖動羔砾,甚至還將它來回攪成一團漿糊:

  1. 數(shù)組存儲著指向游戲實體的指針负间,因此對于數(shù)組中的每個元素而言,我們需要遍歷這些指針——這就引發(fā)了緩存未命中姜凄。
  1. 游戲實體又維護著指向組件的指針政溃,再一次緩存未命中。
  2. 接著我們更新組建态秧。
  3. 現(xiàn)在我們回到步驟1董虱,對游戲里的每個實體的每個組件都這么干。

上面的代碼不斷做著內存空間一日游的操作申鱼,這可不是一個好的現(xiàn)象愤诱。

為了讓循環(huán)想要訪問的實體都在連續(xù)的內存中,我們可以使用一個保存各類控件的大數(shù)組:

AIComponent* aiComponents = new AIComponent[MAX_ENTITIES];
PhysicsComponent* physicsComponent = new PhysicsComponent[MAX_ENTITIES];
RenderComponent* renderComponent = new RenderComponent[MAX_ENTITIES];

然后我們的遍歷就是這樣的:

while(!gameover) {
    for (int i = 0;i < num; ++ i) {
        aiComponents.update();
    }
    for (int i = 0;i < num; ++ i) {
        physicsComponent.update();
    }
    for (int i = 0;i < num; ++ i) {
        renderComponent.update();
    }
}

沒有了指針來跳躍查找數(shù)據(jù)捐友,而是直接對連續(xù)的數(shù)組進行遍歷淫半,一些小的改變就可以避免內存未命中,使CPU效率得到提升匣砖。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末科吭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猴鲫,更是在濱河造成了極大的恐慌对人,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拂共,死亡現(xiàn)場離奇詭異牺弄,居然都是意外死亡,警方通過查閱死者的電腦和手機宜狐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門势告,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛇捌,“玉大人,你說我怎么就攤上這事咱台』砺剑” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵吵护,是天一觀的道長盒音。 經(jīng)常有香客問我,道長馅而,這世上最難降的妖魔是什么祥诽? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮瓮恭,結果婚禮上雄坪,老公的妹妹穿的比我還像新娘。我一直安慰自己屯蹦,他們只是感情好维哈,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著登澜,像睡著了一般阔挠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脑蠕,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天购撼,我揣著相機與錄音,去河邊找鬼谴仙。 笑死迂求,一個胖子當著我的面吹牛,可吹牛的內容都是我干的晃跺。 我是一名探鬼主播揩局,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掀虎!你這毒婦竟也來了凌盯?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤涩盾,失蹤者是張志新(化名)和其女友劉穎十气,沒想到半個月后励背,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體春霍,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年叶眉,在試婚紗的時候發(fā)現(xiàn)自己被綠了址儒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芹枷。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莲趣,靈堂內的尸體忽然破棺而出鸳慈,到底是詐尸還是另有隱情,我是刑警寧澤喧伞,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布走芋,位于F島的核電站,受9級特大地震影響潘鲫,放射性物質發(fā)生泄漏翁逞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一溉仑、第九天 我趴在偏房一處隱蔽的房頂上張望挖函。 院中可真熱鬧,春花似錦浊竟、人聲如沸怨喘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽必怜。三九已至,卻和暖如春后频,著一層夾襖步出監(jiān)牢的瞬間棚赔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工徘郭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留靠益,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓残揉,卻偏偏與公主長得像胧后,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抱环,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內容