# 2017q1 Homework5 (matrix)
contributed by < `baimao8437` >
## 開發環境
```
baimao@baimao-Aspire-V5-573G:~$ lscpu
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
每核心執行緒數:2
每通訊端核心數:2
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 69
Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz
製程: 1
CPU MHz: 1711.242
CPU max MHz: 2600.0000
CPU min MHz: 800.0000
BogoMIPS: 4589.38
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 3072K
NUMA node0 CPU(s): 0-3
```
## ~~閱讀~~欣賞程式碼
### makefile
先拜讀[強者同學精華解析](https://hackmd.io/s/rkFhTyK3x#makefile)
### test-matrix.c
```
MatrixAlgo *matrix_providers[] = {
&NaiveMatrixProvider,
};
```
這可以將`matrix.h`中的所有不同的實作放進array裡依序完成不同的操作
```
/* Available matrix providers */
extern MatrixAlgo NaiveMatrixProvider;
```
但是這樣必須 extern 很多不同的實作方式
我比較偏好 extern 單一 API 介面 像之前在 [phonebook-concurrent](https://github.com/baimao8437/phonebook-concurrent/commit/c818f0a648aad31d9d1488d6f50e4ec6360045bc) 使用的多型技巧 也能保持程式碼簡潔
雖然可能要改寫現有的 makefile 但是再多次測量的設計上也會比較方便、熟悉
## 改寫
### Stopwatch 擴充
又是熟悉的 stopwatch,這次所規定的時間單位要使用 ms
所以我想新增一個 time unit control 的功能
只要 Stopwatch.create("ms") 在 read 時就會回傳單位為 ms 的時間
程式碼大概長這樣 只有三種單位 然後 if else 寫法感覺有點醜...
```c
watch_p create(char *unit)
{
if (!strcmp(unit, "sec"))
S->time_unit = 1000000.0;
else if (!strcmp(unit, "ms"))
S->time_unit = 1000.0;
else if (!strcmp(unit, "us"))
S->time_unit = 1.0;
else
assert(NULL && "time unit error");
...
```
### Makefile 設計
改寫成類似 phonebook-concurrent 的方式 雖然會在 tests 資料夾中生成較多執行檔 但是在設計多次測試 benchmark 會比較方便
而且在`matrix.h`只要 extern 一次 就可以用不同的 method .c檔 去實作不同操作
### test-matrix_%
因為上面的 makefile 修改使得不再用原本將所有方法位置存進 array 的方式執行
可以減少指標的使用 e.g.
將 `algo->assign` 直接改成 `MatrixProvider.assign`
## 站在[巨人的肩膀](https://hackmd.io/s/Hk-llHEyx)
感念前人努力的貢獻 我就心懷感激的當個快樂小碼農
引進 submatrix、sse、sse_prefetch 經適當修改
```
(src1 + (x + 0) * src1_w + k) => & (PRIV(l)->values[(x + 0)][k]))
src2[k * src2_w + y] => PRIV(r)->values[k][y]
```
即可運作 但 avx 還不行 因為現在還只支援 4x4 matrix
## 效能分析
- perf
- naive
```
Performance counter stats for './tests/test-matrix_naive' (10 runs):
2,624 cache-misses # 18.275 % of all cache refs ( +- 23.58% )
14,360 cache-references ( +- 3.02% )
0.000689048 seconds time elapsed ( +- 16.87% )
```
- submatrix
```
Performance counter stats for './tests/test-matrix_submatrix' (10 runs):
1,294 cache-misses # 9.315 % of all cache refs ( +- 48.59% )
13,894 cache-references ( +- 3.40% )
0.000510149 seconds time elapsed ( +- 6.42% )
```
- sse
```
Performance counter stats for './tests/test-matrix_sse' (10 runs):
1,243 cache-misses # 9.022 % of all cache refs ( +- 26.07% )
13,778 cache-references ( +- 1.32% )
0.000488172 seconds time elapsed ( +- 6.02% )
```
- sse_prefetch
```
Performance counter stats for './tests/test-matrix_sse_prefetch' (10 runs):
773 cache-misses # 5.917 % of all cache refs ( +- 49.66% )
13,064 cache-references ( +- 0.54% )
0.000407139 seconds time elapsed ( +- 1.86% )
```
- gnuplot
![](https://i.imgur.com/YfjfQUe.png)
這裡取各執行1000次 計算95信賴區間的結果
## 學習新的包裝法
這段修改是學習[老師的範例](https://github.com/jserv/arith_register) 我全程跪著寫
前面的大修改 把原本的 makefile & matrix provider 的包裝方式改成跟我之前比較熟悉的 phonebook-concurrent 一樣
其實是我畏懼了 面對這次 makefile 的奧妙 我不知從何下手設計 benchmark 只能回頭做我比較熟的東西
老師的範例簡直是一盞明燈 讓我又見識了C語言的深奧
以下是修改的主軸
```c
// in matrix.h
#define REGISTER_MUL(nameX)\
MatrixAlgo MatrixAlgo_##nameX __attribute__((section("MatrixAlgo"))) = { \
.name = #nameX, .assign = assign, .equal = equal, .mul = mul, \
};
/* Available matrix providers */
extern MatrixAlgo __start_MatrixAlgo[], __stop_MatrixAlgo[];
#define MUL_IMPL_BEGIN __start_MatrixAlgo
#define MUL_IMPL_END __stop_MatrixAlgo
```
使得我們新增新的 method 時 不用再去改 matrix.h 帶來很大的便利
只要在各matrix_*method*.c 實作完後 使用macro `REGISTER_MUL(*method name*)` 就會自動對應及分配到 MatrixAlgo 介面 array 中
在測試時只要簡單的
```c
for (MatrixAlgo *p = MUL_IMPL_BEGIN; p < MUL_IMPL_END; p++) {
...
}
```
就可以使用指標 p 去完成不同的實作
目前在想重複的程式碼(assign & equal)該如何處理
讓每個 matrix_*method*.c 檔只對 mul 進行不同的實作