C程式最佳化之路(二) (轉)

amyz發表於2007-10-17
C程式最佳化之路(二) (轉)[@more@]

  本文講述在編寫C程式碼的常用辦法,分為I/O篇,篇,演算法篇,MMX篇。

二.記憶體篇:namespace prefix = o ns = "urn:schemas--com::office" />

  在上一篇中我們講述瞭如何最佳化的讀寫,這一篇則主要講述對記憶體操作的最佳化,主要有陣列的定址,指標連結串列等,還有一些實用技巧。

I.最佳化陣列的定址

  在編寫程式時,我們常常使用一個一維陣列a[M×N]來模擬二維陣列a[N][M],這個時候訪問a[]一維陣列的時候:我們經常是這樣寫a[j×M+i](對於a[j][i])。這樣寫當然是無可置疑的,但是顯然每個定址語句j×M+i都要進行一次乘法運算。現在再讓我們看看二維數值的定址,說到這裡我們不得不深入到C在申請二維陣列和一維陣列的內部細節上――實際在申請二位陣列和一維陣列,編譯器的處理是不一樣的,申請一個a[N][M]的陣列要比申請一個a[M×N]的陣列佔用的空間大!二維陣列的結構是分為兩部分的:

① 是一個指標陣列,的是每一行的起始地址,這也就是為什麼在a[N][M]中,a[j]是一個指標而不是a[j][0]資料的原因。

② 是真正的M×N的連續資料塊,這解釋了為什麼一個二維陣列可以象一維陣列那樣定址的原因。(即a[j][i]等同於(a[0])[j×M+i])

清楚了這些,我們就可以知道二維陣列要比(模擬該二維陣列的)一維陣列定址高。因為a[j][i]的定址僅僅是訪問指標陣列得到j行的地址,然後再+i,是沒有乘法運算的!

  所以,在處理一維陣列的時候,我們常常採用下面的最佳化辦法:(偽碼例子)

  int a[M*N];

  int *b=a;

  for(…){

  b[…]=…;

  …………

  b[…]=…;

  b+=M;

}

這個是遍歷訪問陣列的一個最佳化例子,每次b+=M就使得b為下一行的頭指標。當然如果你願意的話,可以自己定義一個陣列指標來儲存每一行的起始地址。然後按照二維陣列的定址辦法來處理一維陣列。不過,在這裡我建議你乾脆就直接申請一個二維陣列比較的好。下面是動態申請和釋放一個二維陣列的C程式碼。

int get_mem2Dint(int ***array2D, int rows, int columns)  //h.263

{

int i;

 

if((*array2D  = (int**)calloc(rows,  sizeof(int*))) == NULL) no_mem_exit(1);

if(((*array2D)[0] = (int* )calloc(rows*columns,sizeof(int ))) == NULL) no_mem_exit(1);

 

for(i=1 ; i

(*array2D)[i] =  (*array2D)[i-1] + columns  ;

 

return rows*columns*sizeof(int);

}

void free_mem2D(byte **array2D)

{

if (array2D){

    if (array2D[0])  free (array2D[0]);

    else error ("free_mem2D: trying to free unused memory",100);

    free (array2D);

    } else{

    error ("free_mem2D: trying to free unused memory",100);

    }

}

順便說一下,如果你的陣列定址有一個偏移量的話,不要寫為a[x+offset],而應該為 b=a+offset,然後訪問b[x]。

不過,如果你不是處理對速度有特別要求的程式的話,這樣的最佳化也就不必要了。記住,如果編普通程式的話,可讀性和可移值性是第一位的。

 

II.從負數開始的陣列

  在的時候,你是不是經常要處理邊界問題呢?在處理邊界問題的時候,經常下標是從負數開始的,通常我們的處理是將邊界處理分離出來,單獨用額外的程式碼寫。那麼當你知道如何使用從負數開始的陣列的時候,邊界處理就方便多了。下面是靜態使用一個從-1開始的陣列:

int a[M];

int *pa=a+1;

現在如果你使用pa訪問a的時候就是從-1到M-2了,就是這麼簡單。(如果你動態申請a的話,free(a)可不要free(pa)因為pa不是陣列的頭地址)

 

III.我們需要連結串列嗎

  相信大家在學習《資料結構》的時候,對連結串列是相當熟悉了,所以我看有人在編寫一些耗時演算法的時候,也採用了連結串列的形式。這樣編寫當然對記憶體的佔用(似乎)少了,可是速度呢?如果你測試:申請並遍歷10000個元素連結串列的時間與遍歷相同元素的陣列的時間,你就會發現時間相差了百倍!(以前測試過一個演算法,用連結串列是1分鐘,用陣列是4秒鐘)。所以這裡我的建議是:在編寫耗時大的程式碼時,儘可能不要採用連結串列!

  其實實際上採用連結串列並不能真正節省記憶體,在編寫很多演算法的時候,我們是知道要佔用多少記憶體的(至少也知道個大概),那麼與其用連結串列一點點的消耗記憶體,不如用陣列一步就把記憶體佔用。採用連結串列的形式一定是在元素比較少,或者該部分基本不耗時的情況下。

(我估計連結串列主要慢是慢在它是一步步申請記憶體的,如果能夠象陣列一樣分配一個大記憶體塊的話,應該也不怎麼耗時,這個沒有具體測試過。僅僅是猜想 :P)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-977213/,如需轉載,請註明出處,否則將追究法律責任。

相關文章