# 2017q1 Homework5 (matrix)
constributed by <`chenweiii`>
## Makefile
> 此份作業的 Makefile 有點難閱讀 orz [time=Sat, Apr 8, 2017 6:40 PM]
<br>
## 解除 4x4 限制,可以宣告 mxn 矩陣
> 回頭看前幾天寫的,根本不知道在寫殺小,重新整理。[time=Sun, Apr 9, 2017 4:53 PM]
### 修改的程式碼
```clike=
typedef struct {
int row, col;
void *priv;
} Matrix;
typedef struct {
Matrix *(*create)(int row, int col);
void (*assign)(Matrix *thiz, int *data);
bool (*equal)(const Matrix *l, const Matrix *r);
bool (*mul)(Matrix **dst, const Matrix *l, const Matrix *r);
} MatrixAlgo;
```
> 參考了 <```twzjwang```> 大大的 code,才了解原本巨集 PRIV(x) 設計的哲學。但我仍無法解決若矩陣大小在 runtime 才知道,這項技術該如何調整使用? struct naive_priv 在 compile time 就必須知道。 [time=Sun, Apr 9, 2017 12:04 AM]
* 在 MatrixAlgo 新增 create(),事先分配好矩陣 (mxn) 所需之記憶體。
* assign() 則須傳入欲 assign 的陣列之起始位址,讓裡面的資料複製進 Matrix object。
* 一開始不知道為什麼要宣告一個 void *priv,不直接宣告所需的 data type即可?應該是為了程式好擴充(以後也不需要再修改 matrix.h)。
```clike=
struct naive_priv {
int **values;
};
#define PRIV(x) \
((struct naive_priv *) ((x)->priv))
static Matrix *create(int row, int col)
{
Matrix *m = (Matrix *) malloc(sizeof(Matrix));
m->row = row;
m->col = col;
m->priv = malloc(sizeof(struct naive_priv));
PRIV(m)->values = (int **) malloc(row * sizeof(int *));
for(int i = 0; i < row; i++)
PRIV(m)->values[i] = (int *) calloc(col, sizeof(int));
return m;
}
```
* 為了能夠使用 mxn 矩陣,修改了 naive_priv,並在 create() 時注意 pointer to pointer 其要求空間的方式,讓其他函式需要修改的程式碼降到最低。
```clike=
Matrix *m;
m = algo->create(4, 3);
algo->assign(m, (int []) {
1, 2, 3,
5, 6, 7,
1, 2, 3,
5, 6, 7,
});
```
* 修改後的賦值過程。
### 結果
```
Execute tests/test-matrix...
OK!
```
<br>
## 加入 SSE, AVX 等實作
> 使用 SSE 有個限制是 row 及 column 必須為四的倍數,解決方式應該可以添加零列或零行以湊到四的倍數。 [time=Sat, Apr 8, 2017 4:10 PM]
> 先把程式修改為 nxn 可運作,~~且 Matrix struct 裡頭的資料必須為連續的記憶體位置~~。 [time=Sat, Apr 8, 2017 11:01 PM]
> 為能直接移植 SSE, AVX,我先把原本的 data type 改為 int (._.) [time=Sat, Apr 8, 2017 11:17 PM]
> SSE 移植成功! [time=Sun, Apr 9, 2017 11:54 PM]
### 將行跟列湊到四的倍數
```clike=
static Matrix *create(int row, int col)
{
/* ... */
int actual_row = (row % 4 == 0) ? row : row + (4 - row % 4);
int actual_col = (col % 4 == 0) ? col : col + (4 - col % 4);
/* ... */
for(int i = 0; i < actual_row; i++)
PRIV(m)->values[i] = (int *) calloc(actual_col, sizeof(int));
}
```
* actual_row & actual_col 是實際矩陣所拿的空間,並初始化為零。
### 移植 SSE
* 直接移植 [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx) 的成果,並調整會用到的矩陣起始位置。
* 需要注意的是 Matrix dst 實際的大小,應是調整過後的大小
* e.g. A: 4x7, B: 7x5
* 實際大小: A: 4x8, B: 8x8, C:4x8
* 缺點: 浪費過多空間,在此例 dst 便浪費了 37.5% 的空間存放 0。
### 移植 AVX
* 移植 [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx) 的成果
* 將行跟列改成湊到八的倍數
* 遇到的問題
* call _mm256_load_si256() 會產生 Segmentation fault
* 因為記憶體前面沒有去處理對齊,所以只能使用 _mm256_loadu_si256()
* 為什麼 sse 版本明明忘記處理對齊,仍能執行成功?因為 ...
> 影片有講! 但我忘記了,晚點補上。[time=Mon, Apr 10, 2017 1:20 PM]
> ~~TODO: free Matrix dst 未使用的記憶體空間。 [time=Mon, Apr 10, 2017 1:21 PM]~~
<br>
## 測量效能
* 撰寫產生亂數矩陣,並使用 stopwatch
```clike=
int *random_matrix(int row, int col)
{
int *src = (int *) malloc(row * col * sizeof(int));
srand(time(NULL));
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
*(src + i * col + j) = rand();
return src;
}
```
* 使用 stopwatch 計時運算時間
```clike=
for (int j = 0; j < 5; j++) {
MatrixAlgo *algo = matrix_providers[j];
/* ... */
for (int k = 0; k < SAMPLES_NUM; k++) {
Stopwatch.start(ctx);
algo->mul(&dst, l, r);
Stopwatch.reset(ctx);
}
}
```
* 各 implementation 運算不同大小矩陣之圖表 (SAMPLES = 30)
![](https://i.imgur.com/wtdSaeD.png)
<br>
## 學習 crypto API