owned this note
owned this note
Published
Linked with GitHub
# 2016q3 Homework1 (phonebook)
contributed by <`yenWu`>
這次的目標會放在減少 append()時間 和 挑戰題 還有修改 hash function
Code Review: [Mar 8, 2016](https://embedded2016.hackpad.com/Mar-8-2016--kpfgwU6YgOF)
## 開發環境
* 作業系統: Ubuntu 14.04 LTS (64 bit)
* CPU:4-core 2.16 GHz
* Memory:4G
* Cache:
* L1 data : 24kB
* L1 instruction : 32kB
* L2 : 1024kB
使用 `lscpu` 和 `sudo lshw` 可看到硬體資訊
## 學習使用 Github
先前按照老師給的作業指示跟著操作,這次有稍微閱讀一下相關資訊,並且嘗試看看強大的回復功能,和了解一下add的真正意義
* `git init` 建立目錄 `.git`
* `git status` 確認現在所有檔案的狀況
* `git add 檔名` 追蹤此檔案
* `git commit` 確認檔案並snapshot(有點像暫時存檔)
* `git remote add <nickname> <repository-URL>` 連結遠端的repository並給他取代稱
* `git push <nickname>` 把檔案上傳到指定的repository
大概對git的原理和用法有比較基本的認識,但尚未看到branch那邊(有先看fork)
之後會補上SSH的過程(之前都先做完了,沒紀錄到 = =)
## Vim設定
自己寫入設定檔
```
$ vim ~/.vimrc`
```
* vimrc means Run Commands
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457606409847_undefined)
參考資料:
* [vim設定L::檔](http://note.drx.tw/2008/01/vimrc-config.html)
* [鳥哥vim指令](http://linux.vbird.org/linux_basic/0310vi.php)
## astyle 和 pre-commit hook
~~我使用我自己比較偏愛的astyle格式~~
```shell
$astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]
```
~~在 pre-commit hook 我卻沒有成功,我有給 hook/ 底下一個soft link,但我在 commit 時卻沒有偵測我亂寫的格式~~
>> 考慮到團隊合作,請配合原本的指示,使用一致的 coding style [name=jserv]
>> 我已經做了修改了。[name=彥寬]
這邊要說明一下 pre-commit,這邊我搞錯了一件事,就是 softlink 會以 source 會以 target的所在目錄當作相對位置的起點,其實只要 `ll`你就會發現,softlink 顯示就是這樣,所以要去看target的link。
`ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit`
## 效能分析工具 Perf
* `echo -1 > /proc/sys/kernel/perf_event_paranoid`
* `$ ./yourapp & sudo perf top -p $!` 這可以省略寫sleep()再開令一個終端機輸入pid
## Makefile 語法
當只有輸入make時只會進行第一個target,但要執行target前,必須先有後面的相依檔案,如果找不到就會再往下面的target找,把所有相依檔按找完才會執行下面的commond,首先先註記一些較少用到的gcc用法
* 不是依序!應該說每個 target 被 satisfied 後,就會執行指定命令,若回傳值非 0 就會失敗
* gcc後面可以加上:
* `-E` : 只進行前處理,不進行編譯,結果由 console 直接輸出
* `-C` : 進行前處理時保留註解
* `-S` : 產生組合語言程式碼 (.s)
* `-c` : 只編譯不進行連結 (產生 .o 檔)
* `-I` : 指定 INCLUDE PATH
* `-L` : 指定 LIBRARY PATH
* `-llibrar`y -- 指定要連結的函式庫
* `-Dname` : 定義 macro,同 #define
* `-Uname` : 解除 macro 定義,同 #undef
參考資料:
* [GCC](http://www.cmlab.csie.ntu.edu.tw/~daniel/linux/gcc.html)
注意: GCC 是 compiler driver
Makefile 裡頭使用 gcc 的示範:
```shell
CC = gcc
INC = -I ./include
OBJ = main.o op.o
test: ${OBJ}
$(CC) -o $@ ${OBJ}%.o: %.c
${CC} $< ${INC} -c
.PHONY: clean
clean:
rm -f test ${OBJ}
```
* Makefile 基本語法
```shell
目標檔案 (Target): 相依檔案 (Prerequisites)
指令 (Commond)
```
比方說:
```shell
test: main.c op.c
gcc main.c op.c -o test
```
其中
* `CC=gcc` 變數asign
* `${CC}` 取出CC裏面存放的值
* `%.o : %.c` 這是像C++的Template的用法,指令要尋找main.o時,%.o = main.o %.c = main.c
* `$@` = Target
* `$<` = First Dependent File
參考資料:
* [Makefile簡易教學](http://jayextmemo.blogspot.tw/2015/01/linux-gcc-makefile-2.html)
## Gnuplot
這邊我用老師提供的 runtime.gp 當學習範例:
```shell
reset # 這是清除前面的設定
set ylabel 'time(sec)' # 將y軸的label寫上 'time(sec)'
set style fill solid
set title 'perfomance comparison' # 設定 title name
set term png enhanced font 'Verdana,10' # 設定 png 為輸出格式
set output 'runtime.png' # 設定output file name
plot [:][:0.150]'output.txt' using 2:xtic(1) with histogram title ’original’, \
using ($0-0.06):($2+0.001):2 with labels title ' ', \
using 3:xtic(1) with histogram title 'optimized', \
using ($0+0.3):($3+0.0015):3 with labels title ' '
```
最近想要再使用時發生這個問題,進入gnuplot 時畫不出 sin(x) (我想要直接顯示在螢幕),然後看到 terminal type set to ’unknown’,經過查詢得知是抓不到連結,所以方法之一就是更新gnuplot到version 11
參考資料
* [gnuplot安装,及error:terminal type set to 'unknown'的解决](http://blog.csdn.net/deng529828/article/details/24325787)
* [Solution: GNUPlot in Ubuntu - Terminal type set to 'unknown'
](http://lordamit.blogspot.tw/2012/12/solution-gnuplot-in-ubuntu-terminal.html)
* [gnuplot教學](http://gtchen.pixnet.net/blog/post/5873441-gnuplot-(part-i))
## 案例分析:Phonebook
### 未優化版本
`$ make run`
```
size of entry : 136 bytes
execution time of append() : 0.127555 sec
execution time of findName() : 0.015991 sec
```
`$perf stat --repeat 5 -e cycles,instructions,cache-references,cache-misses,branch-instructions,branch-misses ./phonebook_orig`
* ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457162405192_%E6%93%B7%E5%8F%96%E9%81%B8%E5%8F%96%E5%8D%80%E5%9F%9F_010.png)
```
Performance counter stats for ’./phonebook_orig’ (5 runs):
447,819,700 cycles ( +- 1.62% ) [34.16%]
273,082,321 instructions # 0.61 insns per cycle ( +- 0.75% ) [51.33%]
11,489,866 cache-references ( +- 0.81% ) [52.31%]
3,459,780 cache-misses # 30.112 % of all cache refs ( +- 1.59% ) [50.76%]
48,161,780 branch-instructions ( +- 0.58% ) [32.11%]
5,558,063 branch-misses # 11.54% of all branches ( +- 0.71% ) [32.97%]
0.182893634 seconds time elapsed ( +- 0.92% )
```
* `$perf stat -r 10 -e cache-misses,cache-references,L1-dcache-load-misses,L1-dcache-store-misses,L1-dcache-prefetch-misses,L1-icache-load-misses ./phonebook_orig`
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457168347824_%E6%93%B7%E5%8F%96%E9%81%B8%E5%8F%96%E5%8D%80%E5%9F%9F_011.png)
```
Performance counter stats for ’./phonebook_orig’ (10 runs):
3,508,579 cache-misses # 29.720 % of all cache refs ( +- 0.25% ) [49.65%]
11,805,580 cache-references ( +- 0.74% ) [51.67%]
1,347,036 L1-dcache-load-misses ( +- 0.69% ) [50.75%]
<not supported> L1-dcache-store-misses
<not supported> L1-dcache-prefetch-misses
419,659 L1-icache-load-misses ( +- 3.05% ) [48.73%]
0.186848163 seconds time elapsed ( +- 0.60% )
```
### 優化版本:
#### 減少 entry 空間
* 因為搜尋電話簿時,沒必要先把他的資料全部讀到 cache 裡,只需要讀 `lastName` 就好,所以我就把詳細資料跟 `lastName` 分離開來找 。其中 `append()` 和 `findName()` 直接複製orig的
* `phonebook_opt.h`
```C
typedef struct phonebooK_detail {
char firstName[16];
char email[16];
char phone[10];
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
} detail;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
detail *pDetail;
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
```
* 第二次寫我居然犯了一個笨點,就是直接放detail的 struct 而不是 pointer 難怪結果怪怪的,更新新的圖表,結果也差不多
* ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1474660782893_runtime.png)
<s>時間上都有減少,但是append不應該減少才對,我猜想是我再append是貼原本code所以他只會malloc entry的空間,但沒有malloc detail的空間</s>
* 我還沒看完老師的範例就先做了,後來有看到不需要malloc的原因
* 並且發現我並沒有真正看懂題目的意思,append是要建立一個dictionary所以我並不需先malloc
附上detail 也malloc後的圖,結果是append()增高,且findname()也因為這樣而升高了
* 將原本一個的 malloc變成兩個,會對系統造成負擔
<s>![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457364612130_runtime.png)</s>
這次去 dictionary 看,發現搜的字根本不在,本來有再考慮要用 多個東西一起搜讓搜尋更有保有區域性,看來接下來的方法應該要朝分類進行。
## 降低 append() 時間開銷:延後fgets完newline的替換 + 延後補上 detail
利用 fgets的特性,man 完就會發現它會在讀到 newline 或是 EOF停下來並且在他們後面會多種一個 `'\0'`,接著的 code 就是把 newline 變成 `'\0'`,我們延後替換 newline,我操作只比較前面的長度,真的找到了再把 newline 換掉即可。
* `main.c`
```C
while (fgets(line, sizeof(line), fp))
e = append(line, e);
```
* `phonebook_opt.c`
```C
/* because the assert test, we can’t modify the strcut*/
if (strncasecmp(lastname, pHead->lastName, len) == 0 &&
(pHead->lastName[len] == ’\n’ ||
pHead->lastName[len] == ’\0’)) {
pHead->lastName[len] = ’\0’;
return pHead;
```
原本的程式是將字典直接建好,這邊我們先建檢索,等到findName找到時我們再去補上他的資料,這樣也能減少 append()。
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1474669229457_runtime.png)
* 這邊紀錄一下一個 bug `strcpy-sse2-unaligned.S not found` 這只有在 gdb 時會看到,簡單來說就是 strcpy很像有問題,而外面就是直接 core dump,而這個問題即是我的 assert 沒考慮清楚,順帶一提 assert並沒辦法做到即時回傳,因為我的 bug 是出在 assert裏面的 function 所以他會直接 core dump 而不會等到你 assert,簡單說就是我忘了我已經我已經替換了 newline 所以我第二次 call findname 就找不到結果了。
>> 我們應該使用 backtrace 來對付這個,因為他沒有指名是哪個 str出問題。(gdb) [name=彥寬]
## Multi-thread version
~~不說了,悲劇,append大概 0.36 ...~~
這邊我使用mutithread在append上
![](https://i.imgur.com/RnWuX3n.png)
## mmap() preload file to memory space
thread 1:
![](https://i.imgur.com/CXqwo7m.png)
but thread 4 ...:
![](https://i.imgur.com/3cntFLL.png)
...thread 8 ...:
![](https://i.imgur.com/tT4YgQw.png)
>> I notice that more thread and more append() time shake.[name=Yen-Kuan Wu]
>> findName() time will decrease because we use the cross append(), and the benchmark only measure the time we find `zyxel`. [name=Yen-Kuan Wu]
* ==終於找到原因了,原因就是 muti-thread 上使用了 malloc(),由於 malloc system call,所以是根本無法平行化的地方。==
# 使用 "Pthread" + "file alignment" + "mmap" + "簡易 memory pool" + 檢索省略尋找 \`\n\`
thread 1:
![](https://i.imgur.com/ouXKygv.png)
thread 2:
![](https://i.imgur.com/VOd5j2c.png)
thread 4:
![](https://i.imgur.com/JHQSsBI.png)
thread 8:
![](https://i.imgur.com/6zCQjCh.png)
thread 16:
![](https://i.imgur.com/OYfVPrl.png)
* 這邊很清楚的看出來,太多 thread 會變慢沒問題,但奇怪的是 1 2 4的差別卻不大,我也開啟我自己設定的 debug 模式,發現大加搜尋的數目都是正確的,但問題出在我是使用 sync 的方式,所以假如有一個thread跑比較慢,那總體時間也就會比較慢。
* ### 檢索省略尋找 \`\n\`
這就是我上 `降低 append() 時間開銷:延後fgets完newline的替換 + 延後補上 detail`的作法
## Pthread用法
這邊很單純的,我是打算讓大家先各自串一個 link list 之後我再把他連起來,但一開始成效真的是讓人吐血...,經過老師指點,我知道了 fgets 是一個 blocking IO所以就算我開再多 thread,也只是白搭,老師提示我可以用 mmap()。
## mmap() + file alignment
首先想要使用 mmap的動機就是 fgets這個 blocking IO,讓程式更可以平行化,但是由於我們不知道每個字元的 offset,所以我先將 file整理成 16 char alignment,之後再使用 mmap(),就能夠直接 offset 16到下一個 word了。
## 簡易 memory pool
做完面的東西後,發現效能還是沒有多好,而且跟 thread 的使用量不成正比,找了很久很久...,之後才發現是 malloc()再搞鬼,因為 malloc 也屬於 system call,所以他也是無法平行化的東西,所以我們再使用了簡易的memory pool。
為什麼講簡易呢? 因為我就直接 malloc 開linklist 會用到的總空間,然後交叉串聯,當然這不是正統的方式,老師有貼給我正確的 memory pool 用法,他將會是我下一個改善的目標。
## 改善空間
說白了就是 ansyc,由於我是使用 sync的方式,所以 join 一定要等慢的那位,造成我 4 個thread 只要有一個太慢就會拖累總體時間。
## 使用 Hash function
**Hash 42737 bucket**
Hash bucket 我使用老師例子裡的 42737 當作第一次測試,而結果不如預期,append() time 太高了,而我也試圖去找一下問題
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457797060280_undefined)
>「程式輸出的文字訊息」請勿使用圖片表示,請改由文字貼上[name=課程助教]
>不好意思這是從我的 Hackpad貼過來的,年代久遠= =,我記得老師也有提醒過我,我不會再犯的,感謝。[name=彥寬]
* `./phonebook_opt & sudo perf top -p $!`
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457797296650_undefined)
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1457797346545_undefined)
* 我找到了是在這邊,也就是太多碰撞,可能要調整bucket的大小或者直接紀錄最尾巴
```c
{
while( ep->pNext != NULL) {
ep = ep->pNext;
}
```
新增 pLast 減少找尾巴時間
* `hash_bucket`
```c
typedef struct _hash_bucket {
entry *pNext;
entry *pLast;
} hash_bucket;
```
```
Execution time
size of entry : 32 bytes
execution time of append() : 0.168732 sec
execution time of findName() : 0.000003 sec
```
* 還需要繼續改進:
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1458123946888_runtime.png)
* `hash function` 時間過長,要改進
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1458124224189_undefined)
* `append()`
![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_qWv3pIKGt12_p.579576_1458124326056_undefined)
寫到一半有bug,嘗試使用了gdb
```shell
$ gdb ./phonebook.opt
```
就會進入gdb的shell,之後再輸入b(breaking)再來輸入run就能看到我的memory dump掛在那一個function
```shell
(gdb) b
(gdb) run
```
[ntu gdb 筆記](http://www.cmlab.csie.ntu.edu.tw/~daniel/linux/gdb.html)
---
man index 有這個func
###### tags: `yenWu` `phonebook` `sysprog21`