之前在出考題的時候,有出過一題走訪一個二維陣列,然後有二段範例程式,一個是 row by row 來繞,一個是 column by column 來繞,然後詢問哪一個效能比較好。
幾乎所有的人都回答 row by row 的比較好,因為記憶體是連續的。但每當再多問為什麼是連續的就會比較好時,就開始有些人答不上來。(猜測那時候還沒有 AI,大家都只是先 google 完就直接回答)
假設我們有一個 10x10 的二維陣列(最上、最左 是 header),用肉眼比較好看的表達方式如下:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 3 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
| 4 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
| 5 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
| 6 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
| 7 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
| 8 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
| 9 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
底下第一段程式碼是 row by row 的走訪方式,從圖表來看就是 1=>2=>3……10=>11=>12……100 的走訪順序。
// row by row
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
sum += matrix[i, j];
}
}
底下第二段程式碼是 column by column 的走訪方式,從圖表來看就是 1=>11=>21……10=>20=>30……100 的走訪順序。
// column by column
for (int j = 0; j < col; j++) {
for (int i = 0; i < row; i++) {
sum += matrix[i, j];
}
}
之前有一篇文章有提到,cpu 從記憶體拿資料的時候,最小單位都會是 cache line,也就是不會只抓所需要的那個 byte。假設 cpu 是要拿一個int32 的資料,他就會拿一個 cache line,通常就是 64 byte,而這個 cache line 裡面就很有機會含蓋等一下馬上就要使用到的資料。
所以當資料是連續的,並且我們的走訪也是連續的(就像上面提到的 row by row),一個 cache line 讀進來,cpu 等一下需要的資料就有機會在同一個 cache line 裡,然後就能直接使用,不需要再從記憶體拿。
再來是現在的硬體都有預判的功能。也就是程式的寫法如果「可預測性」較高,就像上面的範例程式在走訪陣列,CPU 在從記憶體拿資料的時候,就會再多拿一些後面幾個連續位址的資料,把它們先提前往內移。例:L1 or L2 之類的。如果猜得夠準,也就是這些資料的確是 cpu 等一下就要使用的,那 cpu 就不用再從記憶體拿,而是直接從 L1 or L2 拿,速度就會差很多。
column by column 的走訪方式,很容易讓硬體的預判失效,導致 cpu 找不到資料時,它先從 L1/L2 找,結果還是找不到,就只能每次都從記憶體再一路的往裡面搬。而這每一次的往裡面搬,就是它慢的原因。
這也是之前有提過的,程式如果能又準又快的把 cpu 所需要的資料送給它,整個效能都會有很明顯的提升。
大原則就是剛剛才用到的資料,很有機會在等一下會被再用到。所以就先把它盡量留在靠近 cpu 的地方。像是 L1 or L2。
但各家的機制不同,也就是要留哪些、怎麼留,大家都有各自的演算法。但原則不變,一樣都是猜要留哪些資料,猜中了就能讓 cpu 快點拿到資料。
多理解這些,可能不會讓寫出來的程式突然變得非常快,但至少能讓 IT 在 tune 程式或用資料的時候,可以多一些不同的想法,而不是能動就好……