演算法設計,我們一般都從理論上去考慮一個演算法,即怎麼做才能使得時空複雜度最優。但是在實際情況下把一個演算法完美的實現是一件不容易的事情。所以搞理論的人千萬不要把實現演算法看成很輕鬆的事情,因為你的演算法可能根本在實際中沒有一個有效實現方法,而且即使能夠很容易的實現,可是由於實現的人不瞭解電腦的結構而使得演算法運行起來並不是令人滿意。
例如如下的程式:
01 #include <stdio.h>
02 #include <time.h>
03 #define N 10000
04 #define M 10000
05
06 int sumrow(int (*a)[M], int n, int m)
07 {
08 int sum = 0, i, j;
09 for(i = 0; i < n; ++i)
10 for(j = 0; j < m; ++j)
11 sum += a[i][j];
return sum;
12 }
13
14 int sumcol(int (*a)[M], int n, int m)
15 {
16 int sum = 0, i, j;
17 for(i = 0; i < m; ++i)
18 for(j = 0; j < n; ++j)
19 sum += a[j][i];
return sum;
20 }
21
22 int a[N][M];
23
24 int main()
25 {
26 int pre, nex, sum, i, j;
27
28 for(i = 0; i < N; ++i)
29 for(j = 0; j < M; ++j)
30 a[i][j] = 1;
31
32 pre = clock();
33
34 //sum = sumrow(a, N, M);
35
36 sum = sumcol(a, N, M);
37
38 nex = clock();
39
40 printf("%dms\n", nex - pre); //時間統計
41
42 getchar();
43
44 return 0;
45 }
我的電腦配置: windows xp, AMD Turion 64 X2, 1.9GHz, 1.00G記憶體
sumrow約為800ms,而sumcol為11300ms,相差大約13倍。大家可以想想為什嗎?
我來說說。 這和電腦的儲存結構有關。為了儲存空間能夠跟得上cpu的處理速度,特意在cpu和記憶體之間加入了快取。快取的存取速度可以認為接近cpu的頻率,所以資料如果放到這個裡面那麼我們的程式就快,否則就會慢幾倍甚至10幾倍。但是這個快取的容量有限,所以電腦推測應該把什麼放入裡面才能提高命中率。在我們的這個程式中可以發現,電腦把訪問單元後一小部分連續的單元認為是很有可能訪問的單元,並把其放入告訴緩衝,而C中二維數組是按行儲存的。設快取大小1M,一次能放入262144個整數,10^8個資料按行訪問共出現381次不命中。那麼按列訪問呢,262144個元素只能覆蓋每一列的26行,故訪問每一列的時候,每隔27個元素就有一次不命中,那麼10000行共有371次不命中,所以最差情況下訪問這10^8次方個元素共不命中3710000次。當然計算不準確,但是足以說明問題:sumcol沒有好的空間局部性,而且效率相差10幾倍。所以有的時候最佳化一下代碼實現可能比從理論上來最佳化演算法更有效率。 而如果你要達到這種境界,你就不得不去摸摸電腦這位“小姐”的脾氣。恰恰好玩的是搞理論的絕對不願意去看電腦結構,而搞系統開發的基本不去搞演算法。所以很難能把一個演算法實現的盡善盡美。
在這裡,我只想說,在最佳化程式效率的時候,如果理論上想不出好的辦法,那麼不妨就從電腦體繫結構這方面去改進,畢竟你的程式是在電腦上啟動並執行。