我是用ubuntu15.04 LTS 電腦架構如下:
L1 cache 32K
L2 cache 256K
首先在網頁上fork老師的phonebook,然後用:
$ git clone your git url_
_//譬如:git clone https://github.com/workfunction/phonebook.git
先clone下來你自己的repository到你的電腦裏面,之後開始修改你要修改的檔案
接下來要對每個修改過的檔案用:
$ git add file you edit
//譬如: git add phonebook_opt.c 或是用:git add . 提交全部的檔案進索引
這一步就是在把檔案註冊至索引中。
接著提交修改版本到git中(這裡都是在本地電腦裏面clone下來的repository做修改)用:
$ git commit -m "your change log title"
//譬如: git commit -m "1st opt"
這樣一來你就在電腦裡做好一個修訂版本囉!
注意一點如果打了上面的跳出:
* On branch master
* Your branch is up-to-date with ’origin/master’.
* Changes not staged for commit:
* modified: main.c
* modified: phonebook_opt.c
* modified: phonebook_opt.h
* no changes added to commit
代表他列的那些檔案被修改過可是你忘記add囉,所以就在把他列的檔案add進去吧!
最後就是上傳回去GitHub啦,用下面指令:
$ git push origin master
這樣就可以把你電腦裏面的修訂版本放到GitHub裏面了喔!
push前記得先做astyle再add喔!!astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]
老師說要避免使用 git commit -m,而直接用 git commit -a
,然後把 git commit messages 寫好~
target:
~~~~rule
target2:
~~~~rule line 1; \
~~~~~~~~rule line 2
target: prereq1 prereq2
commands
1. ?=
是一個簡化的語法:若變數未定義,則替它指定新的值。否則,採用原有的值
2. +=
語法CFLAGS = -Wall -g CFLAGS += -O2 此時 CFLAGS 的值就變成 -Wall -g -O2 了
3.若該項目並非檔案,則為 fake 項目。如此一來將不會建立 target 檔案。但為了避免 make 有時會無去判斷 target 是否為檔案或 fake 項目,建議利用 .PHONY 來指定該項目為 fake 項目。例:
* .PHONY: clean
* clean:
* rm *.o
在上例中,若不使用 .PHONY 來指定 clean 為 fake 項目的話,若目錄中同時存在了一個名為 clean 的檔案,則 clean 這個項目將被視為要建立 clean 這個檔案,但 clean 這個項目卻又沒有任何的 dependencies,也因此,clean 項目將永遠被視為 up-to-date,永遠不會被執行。
因為利用了 .PHONY 來指定 clean 為 fake 項目,所以 make 不會去檢查目錄中是否存在了一個名為 clean 的檔案。如此也可以提昇 make 的執行效率。
4.特別字元:
@:不要顯示執行的指令。
-:表示即使該行指令出錯,也不會中斷執行。
5.內部變數:
$?
:代表已被更新的 dependencies 的值,就是 dependencies 中,比 targets 還新的值。
$@
:代表 targets 的值
$<
:代表第一個 dependencies 的值。
$*
:代表 targets 所指定的檔案,但不包含副檔名。
$^
:代表規則中所有相關的檔名,The names of all the prerequisites
* CC ?= gcc
* CFLAGS_common ?= -Wall -std=gnu99 -g
* CFLAGS_orig = -O0
* CFLAGS_opt = -O0
* EXEC = phonebook_orig phonebook_opt phonebook_hash
* all: $(EXEC)
* SRCS_common = main.c
* phonebook_orig: $(SRCS_common) phonebook_orig.c phonebook_orig.h
* $(CC) $(CFLAGS_common) $(CFLAGS_orig) \
* -DIMPL="\"$@.h\"" -o $@ \
* $(SRCS_common) $@.c
* phonebook_opt: $(SRCS_common) phonebook_opt.c phonebook_opt.h
* $(CC) $(CFLAGS_common) $(CFLAGS_opt) \
* -DIMPL="\"$@.h\"" -o $@ \
* -DOPT=1\
* $(SRCS_common) $@.c
-D 後面設定macro. 以phonebook 的makefile為例, (-D 是gcc 的option).
-DIMPL=""$@.h"" 會設定IMPL 為""phonebook_orig.h""
這裡定義的macro 等同於在source code 中的 #define xx
可用 gcc -E 觀察輸出
關於assert 參考(http://linux.die.net/man/3/assert)
man assert(http://linux.die.net/man/3/assert)
清空cache
其中有1 , 2 , 3三個值能修改
echo 3 > /proc/sys/vm/drop_caches
-釋放pagecache、dentries與inodes,也就是釋放所有的cache
echo 2 > /proc/sys/vm/drop_caches
-釋放dentries與inodes
echo 1 > /proc/sys/vm/drop_caches
接下來這裡有點奇怪 make run時有先清空cache,不過當make plot時卻沒有echo 3 | sudo tee /proc/sys/vm/drop_caches
這樣在執行時cache miss 不是就不準了嗎?@@
* run: $(EXEC)
* echo 3 | sudo tee /proc/sys/vm/drop_caches
* **watch** -d -t "./phonebook_orig && echo 3 | sudo tee /proc/sys/vm/drop_caches"
* cache-test: $(EXEC)
* perf stat --repeat 100 \
* -e cache-misses,cache-references,instructions,cycles \
* ./phonebook_orig
* perf stat --repeat 100 \
* -e cache-misses,cache-references,instructions,cycles \
* ./phonebook_opt
* perf stat --repeat 100 \
* -e cache-misses,cache-references,instructions,cycles \
* ./phonebook_hash
* Performance counter stats for ’./phonebook_orig’ (100 runs):
* 5,729,901 cache-misses # 91.576 % of all cache refs
* 6,229,665 cache-references
* 322,355,316 instructions # 1.57 insns per cycle
* 204,877,372 cycles
* 0.069412000 seconds time elapsed ( +- 0.21% )
* Performance counter stats for ’./phonebook_opt’ (100 runs):
* 1,286,653 cache-misses # 64.048 % of all cache refs
* 2,062,259 cache-references
* 281,251,581 instructions # 2.35 insns per cycle
* 118,253,700 cycles
* 0.042995290 seconds time elapsed ( +- 5.16% )
* Performance counter stats for ’./phonebook_hash’ (100 runs):
* 1,533,017 cache-misses # 42.857 % of all cache refs
* 3,566,290 cache-references
* 278,984,877 instructions # 1.54 insns per cycle
* 180,660,117 cycles
* 0.061307312 seconds time elapsed ( +- 0.86% )
這次每個執行前都先清空cache 加入echo 3 | sudo tee /proc/sys/vm/drop_caches
* Performance counter stats for ’./phonebook_orig’ (100 runs):
* 5,619,307 cache-misses # 90.254 % of all cache refs
* 6,146,948 cache-references
* 322,041,533 instructions # 1.57 insns per cycle
* 202,699,556 cycles
* 0.069857174 seconds time elapsed ( +- 0.70% )
* Performance counter stats for ’./phonebook_opt’ (100 runs):
* 1,205,436 cache-misses # 59.680 % of all cache refs
* 1,986,346 cache-references
* 281,165,328 instructions # 2.35 insns per cycle
* 118,036,954 cycles
* 0.041230124 seconds time elapsed ( +- 0.95% )
* Performance counter stats for ’./phonebook_hash’ (100 runs):
* 1,502,643 cache-misses # 42.097 % of all cache refs
* 3,596,799 cache-references
* 277,942,427 instructions # 1.53 insns per cycle
* 180,447,629 cycles
* 0.061681775 seconds time elapsed ( +- 0.95% )
果然先清空cache後每個部份cache-miss都有下降大約好幾萬次~
沒清cache | 有清cache | |
phonebook_orig | 5,729,901 | 5,619,307 |
phonebook_opt | 1,286,653 | 1,205,436 |
phonebook_hash | 1,533,017 | 1,502,643 |
kernel.perf_event_paranoid 是用來決定你在沒有 root 權限下 (Normal User) 使用 perf 時,你可以取得哪些 event data。預設值是 1 ,你可以輸入
$ cat /proc/sys/kernel/perf_event_paranoid
來查看權限值。一共有四種權限值:
2
: 不允許任何量測。但部份用來查看或分析已存在的紀錄的指令仍可使用,如 perf ls、perf report、perf timechart、 perf trace。1
: 不允許 CPU events data。但可以使用 perf stat、perf record 並取得 Kernel profiling data。0
: 不允許 raw tracepoint access。但可以使用 perf stat、perf record 並取得 CPU events data。-1
: 權限全開。最後如果要檢測 cache miss event ,需要先取消 kernel pointer 的禁用。
$ sudo sh -c " echo 0 > /proc/sys/kernel/kptr_restrict"
Perf 能觸發的事件分為三類:
perf 指令:
$ perf top
可以觀察是什麼程序吃掉系統效能,導致系統異常變慢。符號旁顯示**[.]表示其位於 User mode,[k]**則為 kernel mode。
perf stat
主要是已經有個要優化的目標,對這個目標進行特定或一系列的 event 檢查,進而了解該程序的效能概況。
$ perf stat --repeat 5 -e cache-misses,cache-references,instructions,cycles
./perf_stat_cache_miss /--repeat <n>
或是-r <n> 可以重複執行 n 次該程序,並顯示每個 event 的變化區間。 cache-misses,cache-references和 instructions,cycles類似這種成對的 event,若同時出現 perf 會很貼心幫你計算比例usage: perf stat [<options>] [<command>]
-e, –event <event> event selector. use ’perf list’ to list available events
-i, –no-inherit child tasks do not inherit counters
-p, –pid <n> stat events on existing process id
-t, –tid <n> stat events on existing thread id
-a, –all-cpus system-wide collection from all CPUs
-c, –scale scale/normalize counters
-v, –verbose be more verbose (show counter open errors, etc)
-r, –repeat <n> repeat command and print average + stddev (max: 100)
-n, –null null run - dont start any counters
-B, –big-num print large numbers with thousands’ separators
範例測試:
static char array[10000][10000];
int main (void){
int i, j;
for (i = 0; i < 10000; i++)
for(j = 0; j < 10000; j++)
array[j][i]++;
return 0;
}
* Performance counter stats for ’./cache_miss_test’ (5 runs):
* 5,155,920 cache-misses # 2.112 % of all cache refs
* 243,761,851 cache-references
* 2,105,970,720 instructions # 2.02 insns per cycle
* 1,050,026,358 cycles
* 0.334916064 seconds time elapsed ( +- 1.17% )
如果我們利用cache存取的空間區域性(spatial locality),將array[j][i] 改成 array[i][j] 則效果如下:
* Performance counter stats for ’./cache_miss_test2’ (5 runs):
* 1,947,714 cache-misses # 37.618 % of all cache refs
* 5,249,712 cache-references
* 2,105,898,778 instructions # 2.60 insns per cycle
* 811,120,056 cycles
* 0.258861416 seconds time elapsed ( +- 0.83% )
cache-references從243,761,851 降到5,249,712 差了快50倍,時間也減少了0.08秒
先練習範例pi
查看pi這個程式那個部份消耗作多的cpu週期,執行./pi & sudo perf top -p $!,結果顯示compute_pi_baseline函式佔了程式99.78%
看原本Orig的code:
**$./phonebook_orig & perf top -p $! **$!代表最後一個執行的程式
這樣可以讓程式執行並且讓perf可以取的程式的pid去追蹤熱點,不過這邊要輸入三次才會跑出來下面的結果,不知道為什麼?以下可以看到熱點都是在stracasecmp, findName,還有malloc函式
原本程式:
1.先把word.txt name 讀到每一個memory entry, 每個entry 用一個struct來建立,裡面有last name、first name、email..etc. 每個entry是用一個link list串起來
2.append()就是將last Name放入每個entry,每次新增一個entry就放在上一個entry後面
3.fineName()就是去搜尋
可能的效能改進方向:
改寫 struct __PHONE_BOOK_ENTRY 的成員,搬動到新的結構中
使用 hash function 來加速查詢
既然 first name, last name, address 都是合法的英文 (可假設成立),使用字串壓縮的演算法,降低資料表示的成本
使用 binary search tree 改寫演算法
memory pool, work thread
未優化前:
原本的struct entry 大小136 bytes, trace code可以發現都只有在找lastName,其他欄位沒用到。
* typedef struct __PHONE_BOOK_ENTRY {
* char lastName[MAX_LAST_NAME_SIZE];
* 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];
* struct __PHONE_BOOK_ENTRY *pNext;
* } entry;
* size of entry : 136 bytes
* execution time of append() : 0.039642 sec
* execution time of findName() : 0.005316 sec
* Performance counter stats for ’./phonebook_orig’ (100 runs):
* 4,706,050 cache-misses # 94.734 % of all cache refs
* 4,979,890 cache-references
* 262,661,724 instructions # 1.49 insns per cycle
* 176,517,399 cycles
* 0.060470353 seconds time elapsed ( +- 0.32% )
優化後:
這邊採用的方法是讓struct entry size變小,讓更多的entry可以進到cache裡面增加cache-hit機率,原本的size為136 bytes,縮小後變為24 bytes,方法是將struct entry 內的其他欄位註解掉。
* typedef struct __PHONE_BOOK_ENTRY {
* char lastName[MAX_LAST_NAME_SIZE];
* /* 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];*/
* struct __PHONE_BOOK_ENTRY *pNext;
* } entry;
* size of entry : 24 bytes
* execution time of append() : 0.031647 sec
* execution time of findName() : 0.002120 sec
* Performance counter stats for ’./phonebook_opt’ (100 runs):
* 1,045,759 cache-misses # 64.812 % of all cache refs
* 1,606,542 cache-references
* 240,996,877 instructions # 2.20 insns per cycle
* 110,814,772 cycles
* 0.038158386 seconds time elapsed ( +- 0.31% )
Cache miss 由原本的94.734 % 下降了64.812 % 效果不錯~!!
這邊參考了許多同學筆記,決定用djb2的hash funtion, 我先一開始就宣告一個指標陣列,讓它們先指向null,然後每個字串在經過hash funtion 轉成key值之後, 再malloc一個entry給它,這邊我bucket大小先選3733
djb2 hash funtion:
unsigned int hash(char *str) {
unsigned int hash=0;
while(*str){
hash=((hash<<5)+hash)+(*str++);
}
return (hash % SIZE);
}
orig opt hash
append() 0.042059 0.030150 0.038795
findName() 0.005248 0.001807 0.000001
bucket:3733
可以看到分佈還算滿均勻的~
bucket:5381
跟3733的分佈差不多也是滿均勻的~~
因為一直動態配置記憶體會消耗很多時間,所以老師有建議可以用memory pool 方式 ,我這邊是一開始就把memory pool 讓它具有40萬個enrty 的空間 ,這樣malloc 時間應該可以大幅下降
* Performance counter stats for ’./phonebook_orig’ (100 runs):
* 5,605,583 cache-misses # 90.168 % of all cache refs
* 6,146,299 cache-references
* 321,993,358 instructions # 1.55 insns per cycle
* 204,993,902 cycles
* 0.071643834 seconds time elapsed ( +- 0.57% )
* Performance counter stats for ’./phonebook_opt’ (100 runs):
* 1,315,058 cache-misses # 65.543 % of all cache refs
* 1,988,104 cache-references
* 281,276,423 instructions # 2.33 insns per cycle
* 121,999,410 cycles
* 0.042034001 seconds time elapsed ( +- 0.82% )
* Performance counter stats for ’./phonebook_hash’ (100 runs):
* 1,565,930 cache-misses # 41.414 % of all cache refs
* 3,708,770 cache-references
* 278,921,792 instructions # 1.49 insns per cycle
* 186,693,421 cycles
* 0.063751001 seconds time elapsed ( +- 0.54% )
* Performance counter stats for ’./memory_pool’ (100 runs):
* 1,236,034 cache-misses # 44.846 % of all cache refs
* 2,710,477 cache-references
* 307,346,935 instructions # 2.35 insns per cycle
* 127,654,988 cycles
* 0.044987982 seconds time elapsed ( +- 0.82% )
Performance:
orig opt hash hash+memor_ypool
append() 0.041559 0.030556 0.038795 0.027478
findName() 0.005302 0.001871 0.000001 0.000001
speed up
append() 1.36x 1.07x 1.51x
findName() 2.83x 5302x 5302x
cache miss:
phonebook_orig | phonebook_opt | phonebook_hash | memory_pool | |
cache-misses | 5,605,583 | 1,315,058 | 1,565,930 | 1,236,034 |
cache-reference | 6,146,299 | 1,988,104 | 3,708,770 | 2,710,477 |
of all cache refs | 90.168 % | 65.543 % | 41.414 % | 44.846 % |
參考JASON H同學的詳細解說如下:
* reset
* set ylabel ’time(sec)’ `設定Y軸名稱`
* set style fill solid `設定圖形屬性 fill solid是直方圖`
* set title ’perfomance comparison’ `設定設定圖片標題`
* set term png enhanced font ’Verdana,10’ `設定圖片的字體`
* set output ’runtime.png’ `設定輸出檔名`
* plot [:][:0.060]'output.txt' using 2:xtic(1) with histogram title 'original' , \
* '' using ($0-0.1):($2+0.002):2 with labels title ' ' , \
* '' using 3:xtic(1) with histogram title ’'optimized' , \
* '' using ($0+0.1):($3+0.002):3 with labels title ' ' , \
* '' using 4:xtic(1) with histogram title 'hashed' , \
* '' using ($0+0.3):($4+0.0015):4 with labels title ' '
基本語法:using 2:xtic(1) with histogram title ’original’,
’’ using ($0-0.1):($2+0.002):2 with labels title ’ ’,
單引號表示與前面一個檔案相同
第一行using 2
的意思是以output.txt的第二行當作資料, xtic(1)
是用第一行當作X軸的標題(就是append()跟findname())
第二行是來設定直方圖上面的顯示數字位置(x軸位置):(y軸位置)
$0=column(0) 虛擬的行,可以當作檔案中紀錄「列號」的一行
(x-座標):(y-座標):(這裡的數字表示要用那一行的資料) with labels 把數字標在直方圖上,上面有對座標平移避免讓數字不會疊在長條圖上,後方的 title ’ ’可以用 notitle取代
strcasecmp
用到 Intel AVX SIMD 來加速呢?