# 2017q1 Homework5 (matrix)
contributed by < `twzjwang` >
作業說明: [B10: matrix](https://hackmd.io/s/rkrrpHm2e)
github: [twzjwang/matrix_oo](https://github.com/twzjwang/matrix_oo)
# 開發環境
- Ubuntu Ubuntu 16.04.2 LTS
Linux 4.8.0-36-generic
- cpu
version: Intel(R) Core(TM) i5-3337U CPU @ 1.80GHz
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 3072K
- memory
size: 4GiB
# 開發前
## Makefile
- 看不懂這個作業的 Makefile...
- [GNU make](http://www.gnu.org/software/make/manual/make.html)
### 語法
- 宣告變數
- `:=` 與 `=` 差別
- 如果重複宣告,使用 `=` 由 Makefile 中最後一次宣告決定
- 使用 `:=` 根據所在位置決定
- e.g.
- x 為 c
```
a = b
x = $(a)
a = c
```
- x 為 b
```
a := b
x := $(a)
a := c
```
- `?=` 與 `=` 差別
- 若 `?=` 前未宣告才宣告
- 規則
```
target: dependencies
<Tab>Commands
```
- 其他
- `@`+指令 :不要顯示執行的指令。
- `$%` : it matches any target whatever
- `$@` : set to the name of the target
- main.c -> main
- `$^` : a list of all the prerequisites of the rule, including the names of the directories in which they were found, and the value of ‘$@’ is the target
- [字串函數](http://jukpan.blogspot.tw/2009/02/makefile_19.html)
- ` $(addprefix ,) `
- 名稱:加前綴函數——addprefix。
- 功能:把前綴加到中的每個單詞後面。
- 返回:返回加過前綴的文件名序列。
- 示例:$(addprefix src/,foo bar)返回值是「src/foo src/bar」。
$(join ,)
### 解析
- 宣告 `EXEC` `GIT_HOOKS` `OUT`
```
EXEC = \
tests/test-matrix \
tests/test-stopwatch
GIT_HOOKS := .git/hooks/applied
OUT ?= .build
```
- `.PHONY` :
- .PHONY in Makefile
Make 主要的工作目標都是針對檔案,所以萬一你定義的工作目標並不是檔案,或是正好與檔案重複的時候,其實是會讓人丈二金剛摸不著頭的。
所以,.PHONY 被用來定義假工作目標,這樣 Make 就知道這不是針對檔案。[.PHONY](http://blog.xuite.net/tzeng015/twblog/113272262-.PHONY)
- `all`
- 只下 `make` 指令時的 target
- target ` $(GIT_HOOKS) ` ` $(OUT) ` ` $(EXEC) `
- $(GIT_HOOKS)
```
@scripts/install-git-hooks
@echo
```
- 宣告 `CC` `CFLAGS` `LDFLAGS`
```
CC ?= gcc
CFLAGS = -Wall -std=gnu99 -g -O2 -I.
LDFLAGS = -lpthread
```
- 宣告 `OBJS` `deps`
- `OBJS` : stopwatch.o matrix_naive.o
- `deps` : stopwatch.o.d matrix_naive.o.d
- `OBJS` : .build/stopwatch.o .build/matrix_naive.o
- `deps` : .build/stopwatch.o.d .build/matrix_naive.o.d
```
OBJS := \
stopwatch.o \
matrix_naive.o
deps := $(OBJS:%.o=%.o.d)
OBJS := $(addprefix $(OUT)/,$(OBJS))
deps := $(addprefix $(OUT)/,$(deps))
```
- target `tests/test-%`
- 產生所有 `tests/test-%`
- dependencies : `$(OBJS)` 和 `tests/` 下所有 `test-%.c`
```
tests/test-%: $(OBJS) tests/test-%.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
```
- target `$(OUT)/%.o`
- `$(CC) $(CFLAGS) -c -o $@ -MMD -MF $@.d $<`
- `-M` : Instead of outputting the result of preprocessing, output a rule suitable for make describing the dependencies of the main source file.
- `-MM` : link `-M`,(不包含header file)的訊息
- `-MMD` : link `-MM`,並輸出到 `.d` 檔
- `-MF file` : When used with -M or -MM, specifies a file to write the dependencies to
- target `$(OUT)`
- `@mkdir -p $@`
- target `check`
```
@for test in $^ ; \
do \
echo "Execute $$test..." ; $$test && echo "OK!\n" ; \
done
```
- target `clean`
```
$(RM) $(EXEC) $(OBJS) $(deps)
@rm -rf $(OUT)
```
- `-include $(deps)`
- If you want make to simply ignore a makefile which does not exist or cannot be remade, with no error message, use the -include directive instead of include, like this `-include filenames…`
# 開發
## 整合 `SSE`
- 移植 [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx#avx-version) 中用 SSE 實做矩陣乘法的 code
- 實做 AVX2 部份因為 cpu 不支援暫時跳過
- 結果
- `$ make check `
```
Execute tests/test-matrix...
test : Naive
equal!
test : SSE
equal!
OK!
Execute tests/test-stopwatch...
OK!
```
- 使用 `stopwatch` 計時 (只計乘法運算部份)
- `$ make check `
```
Execute tests/test-matrix...
test : Naive
result : equal!
exe time : 0.000001 ms
test : SSE
result : equal!
exe time : 0.000000 ms
OK!
```
- 問題
- 只支援 4x4 大小矩陣
- 矩陣是否能相乘?
- e.g. 1 * 2 跟 3 * 4 不能相乘
- memory leak?
- ...
## 修改只支援 4x4 矩陣的限制
- 修改 `naive_priv` 結構
```C=
struct naive_priv {
float **values;
};
```
- 修改 `static void assign(Matrix *thiz, Mat4x4 *data)`
```C=
struct naive_priv *construct = malloc(sizeof(struct naive_priv));
construct->values = (float **) malloc(thiz->row * sizeof(float *));
for (int i = 0; i < thiz->row; i++)
construct->values[i] = (float *) malloc(thiz->col * sizeof(float));
thiz->priv = construct;
```
- 問題
- 不同大小的 matrix 呼叫 `static void assign(Matrix *thiz, Mat4x4 *data)` 時, 第二個參數 datatype 會不一樣
- 用多型很麻煩針對每個大小要重新定義
- 用 Union ,傳較小的矩陣會浪費空間( Union size 與 size 最大的變數相同),並且要把所有可能的矩陣大小 (Mat1x1 Mat2x2 ...) 設為member
- 改變用其他結構
- 捨棄 `Matmxn` , 直接傳 二維陣列和大小
- `void (*assign)(Matrix *thiz, int row, int col, int **data);`
- 在 user interface 只要改變 `MatrixAlgo`,就可以切換矩陣運算方法
```C=
MatrixAlgo *matrix_providers[] = {
&NaiveMatrixProvider,
&SSEMatrixProvider,
};
MatrixAlgo *algo = matrix_providers[i];
```
- 加上驗證程式與測量效能
- 測試 1 * 1 到 512 * 512 時間,算 50 次取平均
- (SSE 仍只能處理 row 和 col 皆為 4 的倍數的矩陣乘法)
- 後面可嘗試將其他數字補至 4 的倍數解決問題
- 如 3 * 3 填到 4 * 4 不足的地方補 0
```
1 1 1
1 1 1
1 1 1
```
=>
```
1 1 1 0
1 1 1 0
1 1 1 0
0 0 0 0
```
- 結果
- sse 時間遠低於 naive
- (下圖 x y 軸為 log scale)
![](https://i.imgur.com/QrnEitx.png)
n | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512
--|---|---|---|---|----|----|----|-----|-----|-----
naive(10^-6^ms)|0|0|1|2|7|54|366|2765|21930|230525
sse(10^-6^ms) |-|-|0|1|2|12|74|556|4966|45404
- 問題
- 處理不同 datatype 如 float
- SSE 仍只能處理 row 和 col 皆為 4 的倍數的矩陣乘法
- cache
- memory leak?
- data alignment
- thread-safe
- ...
:::info
記憶體管理的議題,可引入 reference counting 技巧,請見: [Generic C Reference Counting](http://nullprogram.com/blog/2015/02/17/) --jserv
:::
## 使用 `posix_memalign`
- 在記憶體沒有對齊的狀況下,可能會需要額外的 cycle 才能完整的抓取到資料。
- [Matrix Multiplication using SIMD 記憶體對齊](https://hackmd.io/s/Hk-llHEyx#記憶體對齊)
- [posix_memalign](http://man7.org/linux/man-pages/man3/posix_memalign.3.html)
- `int posix_memalign(void **memptr, size_t alignment, size_t size);`
- posix_memalign 配置 `size` bytes 給 `*memptr`
- 配置記憶體位置為 `alignment` 的倍數
- alignment 必須是 2 的次方
- alignment 也必須 `sizeof(void *)` 的倍數
- 如果 `size` 為 0, `*memptr` 的值可能為
- NULL
- a unique pointer value that can later be successfully passed to free(3).
- Attribute : Thread safety
- Value : MT-Safe
- can be freed using free(3)
- 結果
- ![](http://i.imgur.com/n0YY3I1.png)
- 使用 malloc 比較快
- 原因
- [malloc](https://linux.die.net/man/3/malloc)
- `The malloc() and calloc() functions return a pointer to the allocated memory that is suitably aligned for any kind of variable`
- 在 32-bit 的電腦上已經以 8 bytes 進行對齊,64-bit 的電腦則是已經以 16 bytes 對齊
- [Matrix Multiplication using SIMD 記憶體對齊](https://hackmd.io/s/Hk-llHEyx#記憶體對齊)
# Reference
- [你所不知道的C語言:技巧篇](https://hackmd.io/s/HyIdoLnjl)
- [Matrix Multiplication using SIMD](https://hackmd.io/s/Hk-llHEyx#matrix-multiplication-using-simd)
- [GNU make](http://www.gnu.org/software/make/manual/make.html)
- [Makefile 語法筆記](http://wandachiang.blogspot.tw/2012/12/makefile.html)
- [Makefile 語法簡介](http://tetralet.luna.com.tw/?op=ViewArticle&articleId=185)
- [字串函數](http://jukpan.blogspot.tw/2009/02/makefile_19.html)
- [GCC online documentation](https://gcc.gnu.org/onlinedocs/)
- [posix_memalign](http://man7.org/linux/man-pages/man3/posix_memalign.3.html)
- [Small-Size Optimization in C](http://nullprogram.com/blog/2016/10/07/)
###### tags: `twzjwang`