2017q1 Homework5(matrix) === contributed by <`vtim9907`> # 開發環境 - OS : Ubuntu 16.04.2 LTS - Kernel Version : 4.8.0-36-generic - Memory : 32 GB - CPU : Intel® Core™ i5-6600 Processor - cache : - L1i cache : 4*32 KB - L1d cache : 4*32 KB - L2 cache : 4*256 KB - L3 cache : 6144 KB - L1 cache imformation ( sudo dmidecode -t cache ) : ``` Handle 0x001E, DMI type 7, 19 bytes Cache Information Socket Designation: L1 Cache Configuration: Enabled, Not Socketed, Level 1 Operational Mode: Write Back Location: Internal Installed Size: 256 kB Maximum Size: 256 kB Supported SRAM Types: Synchronous Installed SRAM Type: Synchronous Speed: Unknown Error Correction Type: Parity System Type: Unified Associativity: 8-way Set-associative ``` # 重現原實驗 ## 修改矩陣只能 4 * 4 的限制 原本作業提供的 code 只能做 4 * 4 的矩陣乘法,所以必須先把這個限制拔除,才好做之後的測試。 首先,我從最開始的 naive 版本開始改。 - 修改 matrix.h 開頭 ```clike= #define M 4 #define N 4 /* predefined shortcut */ #define DECLARE_MATRIX(col, row) \ typedef struct { int values[col][row]; } Mat #if (M >= N) DECLARE_MATRIX(M, M); #else DECLARE_MATRIX(N, N); #endif ``` 矩陣大小由開頭 define 的 M、N 決定。 - 修改 matrix_naive.c 因為矩陣的大小變成動態,所以必須修改原來寫死矩陣大小的 struct ```clike= struct naive_priv { int **values; }; ``` #### `assign(Matrix *thiz, int row, int col, Mat data)` 也因為改成動態,malloc 的方式也改變,主要只是用指標的指標的概念,以完成二維矩陣的 memory 配置 ```clike= struct naive_priv *temp = malloc(sizeof(struct naive_priv)); temp->values = (int **) malloc(dst->row * sizeof(int *)); for (int i = 0; i < dst->row; ++i) temp->values[i] = (int *) malloc(dst->col * sizeof(int)); dst->priv = temp; ``` #### `mul(Matrix *dst, const Matrix *l, const Matrix *r)` 然後在相乘的過程中,必須要做 error handling,因為兩個相乘的矩陣 size 可能不合,不是 $MxN * NxM$,這會造成相乘失敗,所以在相乘之前,先確認兩矩陣的 row 和 column 合不合 ```clike= if (l->col != r->row) { printf("Wrong matrix size!"); return false; } ``` - 修改 test-matrix.c 原本兩個 source matrix 的內容是寫死的,但為了更貼近真實,我改成用 random 的方式,隨機產生矩陣內的資料 ```clike= srand(time(NULL)); for (int i = 0; i < M; ++i) for (int j = 0; j < N; ++j) data1.values[i][j] = rand() % 100; for (int i = 0; i < N; ++i) for (int j = 0; j < M; ++j) data2.values[i][j] = rand() % 100; ``` >怕矩陣的 size 很大時,有可能會產生 overflow 的狀況,所以姑且先讓矩陣內的元素限制在 0 ~ 99 之間。 但是因為資料不是寫死的了,所以原來的正確答案已經不適用,不過因為資料是浮動的,目前還沒有想到一個有效的方法可以動態產生確定正確的答案,所以我只能印出來,在手動確定答案是否正確@@ 試了很多次不同 size 的矩陣,結果都正確。 > 雖然手動驗證有點愚蠢,但若確定這個 naive 版本是正確的,之後就可以以這個版本為標的,未其他版本做驗證。 ### naive 版效能測試 測試 64x128 * 128x256 的矩陣乘法,總共跑 50000 次,取 95% 信賴區間,篩掉誤差過大的數據,圖形如下: ![](https://i.imgur.com/3U1lFcb.png) 基本上平均時間就落在 0.0014s 左右。 ## submatrix 版本效能測試 在 [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx#matrix-multiplication-using-simd) 裡的實驗,其中一個是把矩陣拆成多個 4 * 4 的小矩陣,讓 cache 較有機會發揮作用,但也因此多了一個限制,就是矩陣的 row 和 column 都必須是 4 的倍數。 > 有想到大概的解決方法,不過有點大費周章,再思考比較有效率的解決方法 初步直接修改 naive 為 submatrix 版本( 沒用 AVX , SSE ...): ```clike= for (int i = 0; i < dst->row; i += 4) { for (int j = 0; j < dst->col; j += 4) { for (int k = 0; k < N; k += 4) { for (int i2 = 0; i2 < 4; ++i2) { for (int j2 = 0; j2 < 4; ++j2) { for (int k2 = 0; k2 < 4; ++k2) { PRIV(dst)->values[i + i2][j + j2] += PRIV(l)->values[i + i2][k + k2] * PRIV(r)->values[k + k2][j + j2]; } } } } } } ``` 然後跑看看效能,跟前面條件一樣,都是跑 50000 次,取 95% 信賴區間: ![](https://i.imgur.com/frkEPCy.png) 結果跟原實驗不同,幾乎沒有加速多少,在想可能是因為 data alignment 的問題。 ### memalign() 尋找解決上面問題的過程中,在 [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx#matrix-multiplication-using-simd) 看到 memalign() 這個 function,查了一下 man page 看是在幹什麼的: ``` void *memalign(size_t alignment, size_t size); The obsolete function memalign() allocates size bytes and returns a pointer to the allocated memory. The memory address will be a multiple of alignment, which must be a power of two. ``` 恩... 很好, " obsolete function memalign() " ,記取上次 " register " 的教訓,先看官方的說明果然比較好,看來這個 function 雖然還是可以用,不過被標記為淘汰了,查了 [The GNU C Library: Aligned Memory Blocks](https://www.gnu.org/software/libc/manual/html_node/Aligned-Memory-Blocks.html) ,裡面提到 : ``` The memalign function is obsolete and aligned_alloc or posix_memalign should be used instead. ``` 雖然找不太到為什麼它被淘汰的原因,不過至少知道替代 function 該用哪個,所以又查了一下 aligned_alloc 是作什麼的: ``` void *aligned_alloc(size_t alignment, size_t size); The function aligned_alloc() is the same as memalign(), except for the added restriction that size should be a multiple of alignment. ``` 另外下方還備註 ``` The glibc malloc(3) always returns 8-byte aligned memory addresses, so these functions are only needed if you require larger alignment values. ``` 所以以我現在的狀況來看,我需要對齊的長度至少該大於 8-byte,所以用 malloc 是不夠的,所以合理覺得可以試試用 aligned_alloc 來取代 malloc 配置 matrix。 - aligned_alloc 取代 malloc 來配置 matrix 記憶體空間 因為 aligned_alloc 是 c11 的東西,所以改了 Makefile ( -std=gnu11 ), 然後跑跑看: ![](https://i.imgur.com/4OvACt1.png) 結果還是一樣... 我再想想是哪裡不對 @@@@ > 也許要更換原本用指標的指標配置二維陣列的方式,改攤平成一維的實做方式,說不定有用? # Reference - [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx#matrix-multiplication-using-simd)