《游戲編程模式》筆記——數(shù)據(jù)局部性
意圖
合理組織數(shù)據(jù),充分使用CPU的緩存來加速內(nèi)存讀取。
模式
現(xiàn)代的CPU有緩存來加速內(nèi)存讀取。它可以更快的讀取最近訪問過的內(nèi)存的毗鄰內(nèi)存。通過提高內(nèi)存局部性來提高性能——保證數(shù)據(jù)以處理順序排列在連續(xù)內(nèi)存上。
何時使用
使用數(shù)據(jù)局部性的第一準(zhǔn)則是在遇到性能問題時使用。
就本模式而言,還要確認(rèn)你的性能問題確實由緩存不命中引發(fā),如果是其他原因,這個模式幫不上忙。
設(shè)計決策
如何處理多態(tài)?
不使用多態(tài):
避免子類,至少內(nèi)存優(yōu)化的部分避免使用。簡潔安全,我們明確的知道處理的是什么類,所有的對象大小相同。更快,動態(tài)調(diào)用意味著在跳轉(zhuǎn)表中尋找方法,然后跟著指針尋找特定的代碼。這個小號會因為不同的硬件區(qū)別很大,但動態(tài)調(diào)用總是會有代價。不靈活,使用動態(tài)調(diào)用是為了不同對象間展示不同的行為,可以使用虛方法處理。
為每種類型使用分類的數(shù)組。對象被緊密排列。靜態(tài)調(diào)度。獲得了對象的類型,就不必使用多態(tài),可以使用常規(guī)的非虛方法調(diào)用。但是需要追蹤每個集合,多個類型的時候維護(hù)數(shù)組會很麻煩。由于我們?yōu)槊糠N類型管理分離的集合,我們無法解耦類型集合。
使用指針集合:
使用指針數(shù)組指向基類或者接口類型,可以使用多態(tài)。
靈活,這樣構(gòu)建集合的代碼可以與任何支持接口的類工作,完全開放。
對緩存不友好。指針跳轉(zhuǎn)導(dǎo)致緩存不好有,如果不是性能攸關(guān)的,很有可能行得通。
游戲?qū)嶓w是如何定義的?
如果游戲?qū)嶓w是擁有它組件指針的類:
純OOP的解決方案中,我們擁有游戲?qū)ο箢?,以及指向它擁有的組件的指針,但是不知道組件是如何在內(nèi)存中組織的。
我們可以將實體存儲到連續(xù)數(shù)組中,游戲?qū)嶓w不在乎組件的位置,我們就可以將組件組織到數(shù)組中,優(yōu)化遍歷。
我們拿到一個實體,就可以輕易的獲得它的組件,就在一次指針跳轉(zhuǎn)后的位置。
在內(nèi)存中移動組件很難。組件啟用或關(guān)閉時,如果想要在數(shù)組中移動它們,保證啟用的組件位于前列。如果實體中有指針指向組件時直接移動該組件,指針可能會被銷毀,得保證同時更新指向組件的指針。
如果游戲?qū)嶓w是擁有組件ID的類:
使用ID或索引來查找組件,需要為每個實體保存獨特的ID,遍歷數(shù)組查找,或者使用哈希表將ID映射到組件現(xiàn)有位置。
但是更復(fù)雜,需要實現(xiàn)并排除漏洞,會消耗內(nèi)存。
需要訪問組件“管理器”。基本思路是用抽象ID標(biāo)識組件,以此來獲得對應(yīng)組件對象的引用。但是需要讓ID有辦法找到對應(yīng)的組件。通過裸指針,游戲?qū)嶓w可以直接找到組件,但是需要我們接觸游戲?qū)嶓w和組件注冊器。
如果游戲?qū)嶓w本身就是一個ID:
實體干的唯一事情就是講組件連接在一起,定義了一個存在于游戲世界中的實體。
實體很小,想要傳遞游戲?qū)嶓w的引用時只需要一個簡單的值。
實體是空的,必須將所有組件移出,不能再擁有組件獨有的狀態(tài)和行為,這樣更加依賴組件模式。
不必管理實體的生命周期,實體是內(nèi)置值類型,不需要顯示分配和釋放。實體的所有組件被釋放時,對象就隱式死亡了。
查找實體的某一組件可能會慢。為了找某個實體的組件,需要給ID做對象映射,這一過程消耗可能會很大。
也可以使用組件在數(shù)組中的索引作為ID,需要我們保持組件數(shù)組完全同步,但我們就無法獨自排序某個數(shù)組。
這一章大部分圍繞著組件模式。這種模式的數(shù)據(jù)結(jié)構(gòu)是為緩存優(yōu)化的最常見例子。
這一模式幾乎完全得益于同類對象的連續(xù)存儲數(shù)組,隨著時間推移,我們需要向數(shù)組增加或刪除對象時,可以使用對象池模式。
游戲引擎Artemis是首個也是最著名的為游戲?qū)嶓w使用簡單ID的游戲框架。
參考
《游戲編程模式》