C語言基本功教程系列 高效無錯的記憶體訪問

來源:互聯網
上載者:User

首先說說動態記憶體分配。在c語言裡用的最多的是malloc和free,在c++則是new new[] delete 和delete[]. 這幾個函數是動態記憶體分配的基礎,最常用但也是最佔用CPU資源的系統調用之一.而且在大量使用以後很容易造成記憶體的片段。如果系統記憶體中的片段太多,就會在分配大塊記憶體的時候失敗或者只能在虛擬記憶體上分配記憶體,這就是為什麼有些程式在運行了2,3個小時以後很容易速度不穩定和容易崩潰的原因。另外一個重要的因素就是程式員在寫程式的時候,經常會分配了記憶體而忘記釋放。特別是寫超過 10W行代碼的時候往往忘記了在哪裡分配了記憶體. 所以記憶體的管理對於遊戲的穩定性是非常重要的問題,畢竟大家都是動不動玩上10個小時不休息的主。

目前比較流行的解決方案就是在系統提供的記憶體配置函數上面,寫自己的記憶體管理函數。在C語言裡重寫malloc和free,對每個記憶體的分配和使用方式做追蹤記錄。在C++裡則是重載操作符 new和delete. 通過提供自己的庫,可以很容易檢測到memory leakage. 通過在程式開始的時候從作業系統分配到一塊足夠大的記憶體,在此基礎上進行記憶體管理,還可以有效防止記憶體流失,並且還可以支援對象複用技術,提高遊戲的速度和穩定性。當然,你也可以使用一些memory leakage的偵查工具來檢查記憶體使用量情況(比如 firefox memory leakage detection tool 或者 Visual leak detector)。

實際上,在遊戲程式設計中,很少使用動態記憶體配置,大部分的記憶體都是事先分配好的。即使是鏈表或者是樹這一類的資料結構,也是用數組進行有效類比。

================分割線==================

下面說點代碼裡邊應該注意的問題。在相關記憶體相關的注意事項中,排在第一位的是記憶體對齊問題。也就是說,一塊記憶體的首地址,必須要能被2,4,8,16,32 或者64整除。 不同的CPU對於這個數字有不同的需要。

針對Intel最新發行的 Pentium Dual Core系列 Xenon系列,以及早些日子的 Pentium 4系列。推薦使用64 Bytes 或者 128 Bytes的記憶體對齊。 因為在Pentium4 系列用,每當程式要進行記憶體訪問的時候,CPU的一個預先處理模組(Prefetch)會事先把記憶體中的資料讀到Level1 cache中,並且每次讀入的資料量是 64個 bytes(Pentium Xenon系列是 128 bytes)。 如果沒有進行記憶體對齊, 比如一個int佔用4位元組,第一個位元組在前64bytes中,後3個位元組在後64bytes中,那麼 CPU在讀取這個int的時候就需要多從記憶體中拿一次資料, 會大大增加代碼的已耗用時間。讓我們看下例子:

__declspec(align(64)) int test[128];       // 64位元組對齊

int * pInt = (int *)((char *)test + 1);     // 沒有對齊的指標
int * pInt2 = test;                               // 對齊的指標

int f1(void)
{
int i, k=0; 
for(i = 0; i < 16; i++) k+=pInt[i];
return k;
}

int f2(void)
{
int i, k=0;
for(i = 0; i < 16; i++) k+=pInt2[i];
return k;
}

對照附件中的 VTune的測試結果(見附件1),我們可以看出非64bytes對齊的已耗用時間(clockticks值),幾乎是對齊記憶體的已耗用時間的3 倍。所以在使用動態或者靜態記憶體的時候,最好注意記憶體的字對齊問題。在Visual Studio .Net中,可以用 __declspec (align(64))對靜態變數,數組或者結構進行記憶體對齊。動態記憶體分配可以使用_aligned_malloc() 和  _aligned_free().

這些記憶體對齊的問題,當前的編譯器一般都會幫你最佳化,但是如果要寫自己的記憶體管理函數,就需要分外注意了。

================分割線==================
下面說一下結構數組問題。經常我們會用到結構數組,形式如下:

struct MyStructure{
    int FirstNumber;
    int SecondNumber;
    int ThiredNumber;
    int FourthNumber;
}  StructureArray[100];

這種類型的資料結構,還有另外一種組織的方式,那就是數組結構,形式如下:

struct MyStructure{
    int FirstNumber[100];
    int SecondNumber[100];
    int ThridNumber[100];
    int FourthNumber[100];
} ArrayStructure;

至於這兩種形式用哪種好,要根據具體情況來判斷。一般來說,如果要對所有結構中的同一個成員進行連續的訪問,比如要求100個結構中所有FirstNumber的和,使用第2種形式會快很多。如果要分別求出每個結構所有成員的和,第一種形式要快很多。 

===========求所有結構第一個成員的和==========
// 錯誤的選擇
for(i = 0; i < 100; i++) Sum += StructureArray[i].FirstNumber;
// 正確的選擇
for(i = 0; i < 100; i++) Sum += ArrayStructure.FirstNumber[i];

============求每個結構所有成員的和===========
// 錯誤的選擇
for(i = 0; i < 100; i++) 
    Sum =    ArrayStructure.FirstNumber[i] 
              + ArrayStructure.SecondNumber[i]
              + ArrayStructure.ThirdNumber[i]
              + ArrayStructure.FourthNumber[i];
  
// 正確的選擇
for(i = 0; i < 100; i++) 
    Sum =    StructureArray[i].FirstNumber
              + StructureArray[i].SecondNumber
              + StructureArray[i].ThirdNumber
              + StructureArray[i].FourthNumber;

我想道理不用多說大家也明白了吧, 具體到程式設計中要根據哪種操作用的多來決定資料的組織方式。

關於記憶體訪問,還有很多很多需要注意的事項,比如aliasing問題,store forward問題等等,建議大家去參考intel關於pentium的文檔.

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.