# 2020q3 Homework3 (dict)
contributed by < `ignite1771` >
## 分析 Makefile: perf 在 `make plot` 的行為
```cmake=
# REMINDER: some contexts of Makefile are omitted.
TESTS = test_common
TEST_DATA = s Tai
OBJS_LIB = \
tst.o bloom.o
# Control the build verbosity
ifeq ("$(VERBOSE)","1")
Q :=
VECHO = @true
else
Q := @
VECHO = @printf
endif
test_%: test_%.o $(OBJS_LIB)
$(VECHO) " LD\t$@\n"
$(Q)$(CC) $(LDFLAGS) -o $@ $^ -lm
plot: $(TESTS)
echo 3 | sudo tee /proc/sys/vm/drop_caches;
sudo perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_common --bench CPY $(TEST_DATA) \
| grep 'ternary_tree, loaded 206849 words'\
| grep -Eo '[0-9]+\.[0-9]+' > cpy_data.csv
sudo perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_common --bench REF $(TEST_DATA)\
| grep 'ternary_tree, loaded 206849 words'\
| grep -Eo '[0-9]+\.[0-9]+' > ref_data.csv
```
### 理解過程:$(TESTS)
根據其對應,首先要弄清楚 `test_%` 這個 target 在做什麼:
- 為什麼呢?首先了解 `%` 是萬用字元,代表所有可能字串 (e.g. 例如 test_common 是 test_% 可能的字串之一), 所以 $(TESTS) 變數對到 test_common 值再對到 test_% 此 target, 並且因此會產生其相依目標檔 (prerequisites) 為:
- `test_%.o`
- `$(OBJS_LIB)`
- 至於 `$(VECHO)` 顧名思義為 Verbose Echo, 我們如果給環境變數 `make VERBOSE=1` 時其值為 `@true` , 探討 Makefile 為何有 `@true` 與 `@false` 兩個值的作用:
- 特別字元 `@` 會讓執行的指令不顯示在 stdout 上面
- 參考 [What is “@false” and “@true” in makefile?](https://stackoverflow.com/questions/49756183/what-is-false-and-true-in-makefile) 得知 Makefile 遇到 `@true` 及 `@false` 時分別會呼叫 shell built-in 的 ELF (Executable and Linkable Format) `true` (指令繼續執行,結束後返回 status code = 0) 以及 `false` (指令失敗,返回 status code = 1)
- 可用在終端機用 `$?` 來看上一次指令返回的 status code
- 至於 `" LD\t$@\n"`, 這邊就是如果沒有給 VERBOSE 的話那就會 printf 出來這字串到 stdout
- `$@` 是一個自動化變數,對應到工作目標檔名 (target), 以此例即為 `test_%`
- 至於 `$(Q)` 顧名思義代表 Quiet, 探討為何要用 `:=` 來 assign 其值呢?:
- 參考 [Makefile的賦值運算符(=, :=, +=, ?=)](http://dannysun-unknown.blogspot.com/2015/03/makefile.html) 得知:
- `=` 會讓變數的值在 Makefile 完全展開後才決定
- `:=` 會讓變數的值取決於其在 Makefile 中的位置
簡單的例子如下:
```bash
# =
x = hello
y = $(x) world!
x = hi
all:
@echo $(y)
# make all 會得到 stdout = hi world
# :=
x := hello
y := $(x) world!
x := hi
all:
@echo $(y)
# make all 會得到 stdout = hello world
```
:::warning
判斷在此例 Makefile 中 Q 之內含值指派選用其一應該皆沒有差別,因為非 if 即 else 成立,不知為何還要特別使用 `:=` 呢?
> 程式碼是寫給你們研究和改進的,不是拿來背誦,請不要用探究古文的方式推敲,有想法就去做實驗
> :notes: jserv
我設計實驗 Shell Script 如下,並將用 diff 工具來觀察 stdout 的不同:
```bash=
make all
make clean
make test
make clean
make bench
make clean
make plot
make clean
make all VERBOSE=1
make clean
make test VERBOSE=1
make clean
make bench VERBOSE=1
make clean
make plot VERBOSE=1
make clean
```
- **Control Group**: 原本 Makefile
- **Treatment Group**: 將內容 `:=` 都替換成 `=` (vim 指令:`:%s/:=/=/g`) 的 Makefile
```diff=
@@ -6,14 +6,14 @@ CFLAGS = -O0 -Wall -Werror -g
# Control the build verbosity
ifeq ("$(VERBOSE)","1")
- Q :=
+ Q =
VECHO = @true
else
- Q := @
+ Q = @
VECHO = @printf
endif
-GIT_HOOKS := .git/hooks/applied
+GIT_HOOKS = .git/hooks/applied
.PHONY: all clean
@@ -26,11 +26,11 @@ $(GIT_HOOKS):
OBJS_LIB = \
tst.o bloom.o
-OBJS := \
+OBJS = \
$(OBJS_LIB) \
test_common.o \
-deps := $(OBJS:%.o=.%.o.d)
+deps = $(OBJS:%.o=.%.o.d)
```
- 因為內容會有每次執行時間 (sec) 誤差, 所以透過指令 `seq -i '/sec/d' $FILE` 來刪除有關 sec 字串的 lines
- 最後用 `vimdiff` 來看兩個 stdout 的內容,其二並沒有任何差異
- **結論**:判斷在此例在 Makefile 中用 `=` 可以得到相同的預期結果,沒有必要特別使用 `:=`
:::
- 至於 `${CC}` , 參考 [Variables Used by Implicit Rules](https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html) 知道其為 Program for compiling C programs; default `cc`. 而 `$(LDFLAGS)` 為 Extra flags to give to compilers when they are supposed to invoke the linker, ‘ld’, such as -L. Libraries (-lfoo) should be added to the LDLIBS variable instead.
- 參照 [GNU Ld初探](http://wen00072.github.io/blog/2014/03/14/study-on-the-gnu-ld/) 得知流程為 gcc -> collect2 -> ld
- 至於 `$^` 也是自動化變數,其為所有必要條件的檔名,並以空格隔開這些檔名
- 至於 `-lm` 是因為程式碼有用到 C 的數學函式庫`libm.a` 所以把它一起加進來編譯而丟給 `${CC}` 的參數
以下為參照以上說明之 `make plot` 首先 build prerequisite $(TESTS) 對應 stdout:
```bash=
### OUTPUT of building target $(TESTS) ###
# if VERBOSE=1 enviroment parameter is given
cc -o test_common.o -O0 -Wall -Werror -g -c -MMD -MF .test_common.o.d test_common.c
cc -o tst.o -O0 -Wall -Werror -g -c -MMD -MF .tst.o.d tst.c
cc -o bloom.o -O0 -Wall -Werror -g -c -MMD -MF .bloom.o.d bloom.c
cc -o test_common test_common.o tst.o bloom.o -lm
# else
CC test_common.o
CC tst.o
CC bloom.o
LD test_common
```
### 理解過程:perf 部分
* 用 `perf-stat` 來重複執行 100 次 program 並觀察目標 events 發生的次數,此例中觀察的 cache-misses, cache-references, instructions, cycles 等皆屬於 hardware events
* 抓出 ternary_tree 讀檔 words 建立 TST 的時間:運用 grep 的 `-E` (extended regular expression) 來抓出時間輸出到 csv,藉此比較 $CPY$ 機制與 $REF$ 機制之時間差異 (`make plot` 雖然有給程式 test_common 一個 input `s Tai` 去 search 'Tai' 字串,但是最後只輸出讀檔 words 建立 TST 的時間, 因為 `grep 'ternary_tree, loaded 206849 words' | grep -Eo '[0-9]+\.[0-9]+' > $CSVFILE`)
輸出如下:
```
# CPY
sudo perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_common --bench CPY s Tai \
| grep 'ternary_tree, loaded 206849 words'\
| grep -Eo '[0-9]+\.[0-9]+' > cpy_data.csv
Performance counter stats for './test_common --bench CPY s Tai' (100 runs):
1048313 cache-misses # 36.940 % of all cache refs ( +- 0.26% )
2837872 cache-references ( +- 0.10% )
640085509 instructions # 1.26 insn per cycle ( +- 0.01% )
509712283 cycles ( +- 0.06% )
0.151744 +- 0.000155 seconds time elapsed ( +- 0.10% )
# REF
sudo perf stat --repeat 100 \
-e cache-misses,cache-references,instructions,cycles \
./test_common --bench REF s Tai\
| grep 'ternary_tree, loaded 206849 words'\
| grep -Eo '[0-9]+\.[0-9]+' > ref_data.csv
Performance counter stats for './test_common --bench REF s Tai' (100 runs):
1014360 cache-misses # 35.726 % of all cache refs ( +- 0.32% )
2839305 cache-references ( +- 0.06% )
606467594 instructions # 1.21 insn per cycle ( +- 0.00% )
500247356 cycles ( +- 0.06% )
0.148576 +- 0.000148 seconds time elapsed ( +- 0.10% )
```
可以看出不論是在 $CPY$ 機制還是 $REF$ 機制,在 cache-miss 都有 35% 以上的機率發生在 cache-reference 時,而 IPC 則是在 1.2 以上,雖然 $CPY$ 機制 IPC = 1.26 大於 $REF$ 機制 IPC = 1.21, 但因為 $REF$ 機制少了約 9,464,927 個 cycles,所以其執行時間還是較短,少了約 3 ms
### References
- [Makefile 語法和示範](https://hackmd.io/@sysprog/SySTMXPvl)
- [Linux 效能分析工具: Perf](http://wiki.csie.ncku.edu.tw/embedded/perf-tutorial)